familia 2.0.0.pre15 → 2.0.0.pre17
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/ci.yml +2 -2
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smells.yml +85 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +3 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +54 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +86 -4
- data/CLAUDE.md +39 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +624 -20
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +7 -7
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +51 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +15 -11
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +128 -14
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +12 -171
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/features/autoloader.rb +30 -12
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +71 -66
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +57 -19
- data/lib/familia/features/object_identifier.rb +134 -25
- data/lib/familia/features/quantization.rb +16 -21
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
- data/lib/familia/features/relationships/indexing.rb +182 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +65 -266
- data/lib/familia/features/safe_dump.rb +127 -130
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +10 -7
- data/lib/familia/features.rb +10 -14
- data/lib/familia/field_type.rb +6 -4
- data/lib/familia/horreum/connection.rb +297 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
- data/lib/familia/horreum/serialization.rb +172 -0
- data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
- data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
- data/lib/familia/horreum.rb +222 -119
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -14
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +2 -2
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +120 -2
- data/try/core/connection_try.rb +10 -10
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +11 -10
- data/try/core/errors_try.rb +11 -14
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/familia_try.rb +1 -1
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +3 -3
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/field_groups_try.rb +244 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +443 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +3 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +6 -7
- data/try/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +3 -1
- data/try/horreum/defensive_initialization_try.rb +86 -0
- data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/horreum/settings_try.rb +2 -0
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +2 -2
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +13 -15
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- data/try/valkey.conf +26 -0
- metadata +92 -52
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -198
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/horreum/core/connection.rb +0 -73
- data/lib/familia/horreum/core.rb +0 -21
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -35,8 +35,8 @@ stripe_customer.class.name
|
|
35
35
|
#=> "Familia::HashKey"
|
36
36
|
|
37
37
|
## DataType instances know their owner
|
38
|
-
@sample_obj.timeline.parent
|
39
|
-
#=>
|
38
|
+
@sample_obj.timeline.parent.class
|
39
|
+
#=> Familia::Horreum::ParentDefinition
|
40
40
|
|
41
41
|
## DataType instances know their field name
|
42
42
|
@sample_obj.timeline.keystring
|
@@ -54,7 +54,7 @@ stripe_customer.class.name
|
|
54
54
|
#==> _.respond_to?(:exists?)
|
55
55
|
#=/=> _.respond_to?(:destroy!)
|
56
56
|
|
57
|
-
## Can check if DataType exists in Redis
|
57
|
+
## Can check if DataType exists in Valkey/Redis
|
58
58
|
timeline = @sample_obj.timeline
|
59
59
|
exists_before = timeline.exists?
|
60
60
|
[exists_before.class, [true, false].include?(exists_before)]
|
@@ -44,7 +44,8 @@ obj = Customer.find_by_id :delano
|
|
44
44
|
|
45
45
|
## Customer.destroy
|
46
46
|
@cust.destroy!
|
47
|
-
|
47
|
+
#=:> MultiResult
|
48
|
+
#==> result.successful?
|
48
49
|
|
49
50
|
## Customer.instances
|
50
51
|
Customer.values.size
|
@@ -56,20 +57,20 @@ obj.save
|
|
56
57
|
#=> true
|
57
58
|
|
58
59
|
## Familia.class_list
|
59
|
-
Customer.
|
60
|
-
#=> Familia::
|
60
|
+
Customer.all_customers.class
|
61
|
+
#=> Familia::ListKey
|
61
62
|
|
62
63
|
## Familia class dbkey
|
63
|
-
Customer.
|
64
|
-
#=> 'customer:
|
64
|
+
Customer.all_customers.dbkey
|
65
|
+
#=> 'customer:all_customers'
|
65
66
|
|
66
67
|
## Familia.class_list
|
67
|
-
Customer.
|
68
|
-
Customer.
|
68
|
+
Customer.all_customers << :delano << :tucker << :morton
|
69
|
+
Customer.all_customers.size
|
69
70
|
#=> 3
|
70
71
|
|
71
72
|
## Familia class clear
|
72
|
-
Customer.
|
73
|
+
Customer.all_customers.delete!
|
73
74
|
#=> true
|
74
75
|
|
75
76
|
## Familia class replace 1 of 4
|
@@ -10,7 +10,7 @@ user_class = Class.new(Familia::Horreum) do
|
|
10
10
|
field :data
|
11
11
|
end
|
12
12
|
|
13
|
-
large_data = { items: (1..1000).to_a
|
13
|
+
large_data = { metadata: "x" * 1000, items: (1..1000).to_a }
|
14
14
|
|
15
15
|
json_time = Benchmark.realtime do
|
16
16
|
100.times { JSON.dump(large_data) }
|
@@ -44,7 +44,7 @@ users.each(&:delete!)
|
|
44
44
|
individual_time > 0
|
45
45
|
#=!> StandardError
|
46
46
|
|
47
|
-
## Redis type access performance
|
47
|
+
## Valkey/Redis type access performance
|
48
48
|
user_class = Class.new(Familia::Horreum) do
|
49
49
|
identifier_field :email
|
50
50
|
field :name
|
@@ -6,10 +6,10 @@
|
|
6
6
|
# The issue is that refresh! is being called within the transaction, but
|
7
7
|
# Database MULTI transactions queue commands and don't return results until
|
8
8
|
# EXEC. So refresh! inside the transaction isn't going to see the current
|
9
|
-
# state from
|
9
|
+
# state from the database.
|
10
10
|
#
|
11
11
|
# The problem is more fundamental: Database MULTI/EXEC transactions don't
|
12
|
-
# work the way this code expects them to. In Redis:
|
12
|
+
# work the way this code expects them to. In Valkey/Redis:
|
13
13
|
#
|
14
14
|
# 1. MULTI starts queuing commands
|
15
15
|
# 2. All subsequent commands are queued, not executed
|
@@ -20,7 +20,7 @@
|
|
20
20
|
# work as expected
|
21
21
|
# 2. Read the current balance and modify it - this won't work inside MULTI
|
22
22
|
#
|
23
|
-
# The atomic operations need to be restructured to work with Redis's
|
23
|
+
# The atomic operations need to be restructured to work with Valkey/Redis's
|
24
24
|
# actual transaction model. Let me fix this:
|
25
25
|
|
26
26
|
require 'bundler/setup'
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# connection or MULTI connection based on Thread-local context
|
13
13
|
# 2. **Thread Safety**: Uses Thread-local storage for transaction state
|
14
14
|
# 3. **No method_missing**: Clean implementation via method overriding
|
15
|
-
# 4. **Database MULTI/EXEC**: Leverages Redis's native transaction support
|
15
|
+
# 4. **Database MULTI/EXEC**: Leverages Valkey/Redis's native transaction support
|
16
16
|
#
|
17
17
|
# Design Decision: TransactionalMethods Module REMOVED
|
18
18
|
#
|
@@ -70,7 +70,7 @@ class TransactionRecord < Familia::Horreum
|
|
70
70
|
@to_account = to
|
71
71
|
@amount = amount.to_f
|
72
72
|
@status = "pending"
|
73
|
-
@created_at =
|
73
|
+
@created_at = Familia.now.to_i
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -78,11 +78,11 @@ end
|
|
78
78
|
module Familia
|
79
79
|
class << self
|
80
80
|
def current_transaction
|
81
|
-
|
81
|
+
Fiber[:familia_current_transaction]
|
82
82
|
end
|
83
83
|
|
84
84
|
def current_transaction=(transaction)
|
85
|
-
|
85
|
+
Fiber[:familia_current_transaction] = transaction
|
86
86
|
end
|
87
87
|
|
88
88
|
def atomic(&block)
|
@@ -83,7 +83,7 @@ class TransactionRecord < Familia::Horreum
|
|
83
83
|
@to_account = to
|
84
84
|
@amount = amount.to_f
|
85
85
|
@status = "pending"
|
86
|
-
@created_at =
|
86
|
+
@created_at = Familia.now.to_i
|
87
87
|
end
|
88
88
|
|
89
89
|
def amount
|
@@ -121,11 +121,11 @@ module Familia
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def current_transaction
|
124
|
-
|
124
|
+
Fiber[:familia_current_transaction_v3]
|
125
125
|
end
|
126
126
|
|
127
127
|
def current_transaction=(transaction)
|
128
|
-
|
128
|
+
Fiber[:familia_current_transaction_v3] = transaction
|
129
129
|
end
|
130
130
|
|
131
131
|
# Proxy approach - transparent like V2
|
@@ -135,7 +135,7 @@ module Familia
|
|
135
135
|
atomic_separate(&block)
|
136
136
|
else
|
137
137
|
# Use connection pool to get connection
|
138
|
-
# For this prototype, we'll use a simple approach that works with Redis
|
138
|
+
# For this prototype, we'll use a simple approach that works with Valkey/Redis
|
139
139
|
connection_pool.with do |conn|
|
140
140
|
begin
|
141
141
|
# Store the connection for use within the block
|
@@ -83,7 +83,7 @@ class TransactionRecord < Familia::Horreum
|
|
83
83
|
@to_account = to
|
84
84
|
@amount = amount.to_f
|
85
85
|
@status = "pending"
|
86
|
-
@created_at =
|
86
|
+
@created_at = Familia.now.to_i
|
87
87
|
end
|
88
88
|
|
89
89
|
def amount
|
@@ -121,11 +121,11 @@ module Familia
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def current_transaction
|
124
|
-
|
124
|
+
Fiber[:familia_current_transaction_v3]
|
125
125
|
end
|
126
126
|
|
127
127
|
def current_transaction=(transaction)
|
128
|
-
|
128
|
+
Fiber[:familia_current_transaction_v3] = transaction
|
129
129
|
end
|
130
130
|
|
131
131
|
# Proxy approach - transparent like V2
|
@@ -135,7 +135,7 @@ module Familia
|
|
135
135
|
atomic_separate(&block)
|
136
136
|
else
|
137
137
|
# Use connection pool to get connection
|
138
|
-
# For this prototype, we'll use a simple approach that works with Redis
|
138
|
+
# For this prototype, we'll use a simple approach that works with Valkey/Redis
|
139
139
|
connection_pool.with do |conn|
|
140
140
|
begin
|
141
141
|
# Store the connection for use within the block
|
@@ -26,7 +26,7 @@ module ConnectionPoolMetrics
|
|
26
26
|
thread_id: thread_id,
|
27
27
|
wait_time: wait_time,
|
28
28
|
acquired: acquired,
|
29
|
-
timestamp:
|
29
|
+
timestamp: Familia.now
|
30
30
|
}
|
31
31
|
end
|
32
32
|
end
|
@@ -37,7 +37,7 @@ module ConnectionPoolMetrics
|
|
37
37
|
thread_id: thread_id,
|
38
38
|
state: state, # :waiting, :running, :completed, :failed
|
39
39
|
context: context,
|
40
|
-
timestamp:
|
40
|
+
timestamp: Familia.now
|
41
41
|
}
|
42
42
|
end
|
43
43
|
end
|
@@ -47,7 +47,7 @@ module ConnectionPoolMetrics
|
|
47
47
|
@metrics[:pool_exhaustion_events] << {
|
48
48
|
wait_time: wait_time,
|
49
49
|
threads_waiting: thread_count_waiting,
|
50
|
-
timestamp:
|
50
|
+
timestamp: Familia.now
|
51
51
|
}
|
52
52
|
end
|
53
53
|
end
|
@@ -99,7 +99,7 @@ module ConnectionPoolMetrics
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def export_detailed_csv(filename_prefix = "stress_test")
|
102
|
-
timestamp =
|
102
|
+
timestamp = Familia.now.strftime("%Y%m%d_%H%M%S")
|
103
103
|
|
104
104
|
# Export operations
|
105
105
|
CSV.open("#{filename_prefix}_operations_#{timestamp}.csv", "w") do |csv|
|
@@ -274,7 +274,7 @@ module ConnectionPoolMetrics
|
|
274
274
|
|
275
275
|
def add_result(config, metrics_summary, model_info = {})
|
276
276
|
@results << {
|
277
|
-
timestamp:
|
277
|
+
timestamp: Familia.now,
|
278
278
|
config: config,
|
279
279
|
summary: metrics_summary,
|
280
280
|
model: model_info
|
@@ -260,7 +260,7 @@ class MetricsCollector
|
|
260
260
|
duration: duration,
|
261
261
|
success: success,
|
262
262
|
wait_time: wait_time,
|
263
|
-
timestamp:
|
263
|
+
timestamp: Familia.now
|
264
264
|
}
|
265
265
|
|
266
266
|
if @use_concurrent
|
@@ -277,7 +277,7 @@ class MetricsCollector
|
|
277
277
|
error: error.class.name,
|
278
278
|
message: error.message,
|
279
279
|
context: context,
|
280
|
-
timestamp:
|
280
|
+
timestamp: Familia.now
|
281
281
|
}
|
282
282
|
|
283
283
|
if @use_concurrent
|
@@ -301,7 +301,7 @@ class MetricsCollector
|
|
301
301
|
utilization: utilization,
|
302
302
|
utilization_alt: utilization_alt,
|
303
303
|
in_use: in_use,
|
304
|
-
timestamp:
|
304
|
+
timestamp: Familia.now
|
305
305
|
}
|
306
306
|
|
307
307
|
if @use_concurrent
|
@@ -582,17 +582,17 @@ class ConnectionPoolStressTest
|
|
582
582
|
@config[:operations_per_thread].times do |op_index|
|
583
583
|
account = get_account_for_operation(i, op_index)
|
584
584
|
begin
|
585
|
-
start =
|
586
|
-
wait_start =
|
585
|
+
start = Familia.now
|
586
|
+
wait_start = Familia.now
|
587
587
|
|
588
588
|
Familia.atomic do
|
589
|
-
wait_time =
|
589
|
+
wait_time = Familia.now - wait_start
|
590
590
|
account.complex_operation
|
591
|
-
@metrics.record_operation(:transaction,
|
591
|
+
@metrics.record_operation(:transaction, Familia.now - start, true, wait_time)
|
592
592
|
end
|
593
593
|
rescue => e
|
594
594
|
@metrics.record_error(e, { thread: i })
|
595
|
-
@metrics.record_operation(:transaction,
|
595
|
+
@metrics.record_operation(:transaction, Familia.now - start, false)
|
596
596
|
end
|
597
597
|
end
|
598
598
|
rescue => e
|
@@ -629,12 +629,12 @@ class ConnectionPoolStressTest
|
|
629
629
|
puts "Running rapid fire test with #{@config[:thread_count]} threads"
|
630
630
|
|
631
631
|
if @config[:duration]
|
632
|
-
end_time =
|
632
|
+
end_time = Familia.now + @config[:duration]
|
633
633
|
|
634
634
|
@config[:thread_count].times do |i|
|
635
635
|
threads << Thread.new do
|
636
636
|
op_index = 0
|
637
|
-
while
|
637
|
+
while Familia.now < end_time
|
638
638
|
account = get_account_for_operation(i, op_index)
|
639
639
|
operation = select_operation
|
640
640
|
execute_operation(account, operation)
|
@@ -691,7 +691,7 @@ class ConnectionPoolStressTest
|
|
691
691
|
|
692
692
|
@config[:operations_per_thread].times do
|
693
693
|
begin
|
694
|
-
start =
|
694
|
+
start = Familia.now
|
695
695
|
|
696
696
|
Familia.atomic do
|
697
697
|
# Simulate long-running transaction
|
@@ -706,10 +706,10 @@ class ConnectionPoolStressTest
|
|
706
706
|
account2.save
|
707
707
|
end
|
708
708
|
|
709
|
-
@metrics.record_operation(:long_transaction,
|
709
|
+
@metrics.record_operation(:long_transaction, Familia.now - start, true)
|
710
710
|
rescue => e
|
711
711
|
@metrics.record_error(e, { thread: i })
|
712
|
-
@metrics.record_operation(:long_transaction,
|
712
|
+
@metrics.record_operation(:long_transaction, Familia.now - start, false)
|
713
713
|
end
|
714
714
|
end
|
715
715
|
end
|
@@ -731,7 +731,7 @@ class ConnectionPoolStressTest
|
|
731
731
|
|
732
732
|
@config[:operations_per_thread].times do
|
733
733
|
begin
|
734
|
-
start =
|
734
|
+
start = Familia.now
|
735
735
|
|
736
736
|
Familia.atomic do
|
737
737
|
account.deposit(50)
|
@@ -747,10 +747,10 @@ class ConnectionPoolStressTest
|
|
747
747
|
account.save
|
748
748
|
end
|
749
749
|
|
750
|
-
@metrics.record_operation(:nested_transaction,
|
750
|
+
@metrics.record_operation(:nested_transaction, Familia.now - start, true)
|
751
751
|
rescue => e
|
752
752
|
@metrics.record_error(e, { thread: i })
|
753
|
-
@metrics.record_operation(:nested_transaction,
|
753
|
+
@metrics.record_operation(:nested_transaction, Familia.now - start, false)
|
754
754
|
end
|
755
755
|
end
|
756
756
|
end
|
@@ -773,7 +773,7 @@ class ConnectionPoolStressTest
|
|
773
773
|
|
774
774
|
@config[:operations_per_thread].times do |op_num|
|
775
775
|
begin
|
776
|
-
start =
|
776
|
+
start = Familia.now
|
777
777
|
|
778
778
|
if rand < error_rate
|
779
779
|
# Inject an error
|
@@ -784,10 +784,10 @@ class ConnectionPoolStressTest
|
|
784
784
|
account.complex_operation
|
785
785
|
end
|
786
786
|
|
787
|
-
@metrics.record_operation(:with_errors,
|
787
|
+
@metrics.record_operation(:with_errors, Familia.now - start, true)
|
788
788
|
rescue => e
|
789
789
|
@metrics.record_error(e, { thread: i, operation: op_num })
|
790
|
-
@metrics.record_operation(:with_errors,
|
790
|
+
@metrics.record_operation(:with_errors, Familia.now - start, false)
|
791
791
|
end
|
792
792
|
end
|
793
793
|
end
|
@@ -811,12 +811,12 @@ class ConnectionPoolStressTest
|
|
811
811
|
|
812
812
|
def run_duration_based_test(mix)
|
813
813
|
threads = []
|
814
|
-
end_time =
|
814
|
+
end_time = Familia.now + @config[:duration]
|
815
815
|
|
816
816
|
@config[:thread_count].times do |i|
|
817
817
|
threads << Thread.new do
|
818
818
|
op_index = 0
|
819
|
-
while
|
819
|
+
while Familia.now < end_time
|
820
820
|
account = get_account_for_operation(i, op_index)
|
821
821
|
operation = select_operation_from_mix(mix)
|
822
822
|
execute_operation(account, operation)
|
@@ -898,18 +898,18 @@ class ConnectionPoolStressTest
|
|
898
898
|
|
899
899
|
def execute_operation(account, operation)
|
900
900
|
begin
|
901
|
-
start =
|
901
|
+
start = Familia.now
|
902
902
|
|
903
903
|
case operation
|
904
904
|
when :read
|
905
905
|
account.refresh!
|
906
906
|
_ = account.balance
|
907
|
-
@metrics.record_operation(:read,
|
907
|
+
@metrics.record_operation(:read, Familia.now - start, true)
|
908
908
|
when :write
|
909
909
|
current = account.balance || 0
|
910
910
|
account.balance = current + rand(-10..10)
|
911
911
|
account.save
|
912
|
-
@metrics.record_operation(:write,
|
912
|
+
@metrics.record_operation(:write, Familia.now - start, true)
|
913
913
|
when :transaction
|
914
914
|
Familia.atomic do
|
915
915
|
account.refresh!
|
@@ -917,12 +917,12 @@ class ConnectionPoolStressTest
|
|
917
917
|
account.balance = current + rand(-10..10)
|
918
918
|
account.save
|
919
919
|
end
|
920
|
-
@metrics.record_operation(:transaction,
|
920
|
+
@metrics.record_operation(:transaction, Familia.now - start, true)
|
921
921
|
end
|
922
922
|
rescue => e
|
923
923
|
puts "Operation error: #{e.message} (#{e.class})" if ENV['FAMILIA_DEBUG']
|
924
924
|
@metrics.record_error(e, { operation: operation })
|
925
|
-
@metrics.record_operation(operation,
|
925
|
+
@metrics.record_operation(operation, Familia.now - start, false)
|
926
926
|
end
|
927
927
|
end
|
928
928
|
|
@@ -227,24 +227,24 @@ module ThreadingModels
|
|
227
227
|
end
|
228
228
|
|
229
229
|
def perform_operation(operation, account)
|
230
|
-
start =
|
230
|
+
start = Familia.now
|
231
231
|
|
232
232
|
case operation[:type]
|
233
233
|
when :read
|
234
234
|
account.refresh!
|
235
|
-
{ success: true, duration:
|
235
|
+
{ success: true, duration: Familia.now - start }
|
236
236
|
when :write
|
237
237
|
account.balance += operation[:amount] || 0
|
238
238
|
account.save
|
239
|
-
{ success: true, duration:
|
239
|
+
{ success: true, duration: Familia.now - start }
|
240
240
|
when :transaction
|
241
241
|
Familia.atomic do
|
242
242
|
account.complex_operation
|
243
243
|
end
|
244
|
-
{ success: true, duration:
|
244
|
+
{ success: true, duration: Familia.now - start }
|
245
245
|
end
|
246
246
|
rescue => e
|
247
|
-
{ success: false, error: e, duration:
|
247
|
+
{ success: false, error: e, duration: Familia.now - start }
|
248
248
|
end
|
249
249
|
end
|
250
250
|
|
@@ -334,7 +334,7 @@ class EnhancedConnectionPoolStressTest < ConnectionPoolStressTest
|
|
334
334
|
|
335
335
|
puts "\n=== Running with #{model_name} model ==="
|
336
336
|
|
337
|
-
start_time =
|
337
|
+
start_time = Familia.now
|
338
338
|
|
339
339
|
result = model.run do |account, thread_id, op_num|
|
340
340
|
operation = select_operation_from_mix(
|
@@ -343,7 +343,7 @@ class EnhancedConnectionPoolStressTest < ConnectionPoolStressTest
|
|
343
343
|
execute_operation(account, operation)
|
344
344
|
end
|
345
345
|
|
346
|
-
duration =
|
346
|
+
duration = Familia.now - start_time
|
347
347
|
|
348
348
|
result.merge(
|
349
349
|
total_duration: duration,
|
@@ -26,7 +26,7 @@ class PoolSiege
|
|
26
26
|
if @options[:profile]
|
27
27
|
run_with_profiling
|
28
28
|
else
|
29
|
-
start_time =
|
29
|
+
start_time = Familia.now
|
30
30
|
|
31
31
|
if @options[:quiet]
|
32
32
|
run_silent_test
|
@@ -34,7 +34,7 @@ class PoolSiege
|
|
34
34
|
run_with_progress
|
35
35
|
end
|
36
36
|
|
37
|
-
end_time =
|
37
|
+
end_time = Familia.now
|
38
38
|
print_final_results(end_time - start_time)
|
39
39
|
end
|
40
40
|
end
|
@@ -157,7 +157,7 @@ class PoolSiege
|
|
157
157
|
end
|
158
158
|
end.parse!(args)
|
159
159
|
|
160
|
-
#
|
160
|
+
# UnsortedSet defaults
|
161
161
|
options[:threads] ||= 10
|
162
162
|
options[:pool_size] ||= 5
|
163
163
|
options[:operations] ||= 100 unless options[:duration]
|
@@ -310,16 +310,16 @@ class PoolSiege
|
|
310
310
|
end
|
311
311
|
|
312
312
|
# Run the test
|
313
|
-
start_time =
|
313
|
+
start_time = Familia.now
|
314
314
|
run_silent_test
|
315
|
-
end_time =
|
315
|
+
end_time = Familia.now
|
316
316
|
total_time = end_time - start_time
|
317
317
|
|
318
318
|
# Restore original method
|
319
319
|
Familia.define_singleton_method(:atomic, original_atomic)
|
320
320
|
|
321
321
|
# Generate report
|
322
|
-
timestamp =
|
322
|
+
timestamp = Familia.now.strftime("%Y%m%d_%H%M%S")
|
323
323
|
scenario_name = @options[:scenario].to_s
|
324
324
|
report_file = "pool_siege_#{scenario_name}_#{timestamp}_profile.txt"
|
325
325
|
|
@@ -439,8 +439,8 @@ class ProgressTracker
|
|
439
439
|
@total_ops = total_ops
|
440
440
|
@completed = 0
|
441
441
|
@successful = 0
|
442
|
-
@start_time =
|
443
|
-
@last_update =
|
442
|
+
@start_time = Familia.now
|
443
|
+
@last_update = Familia.now
|
444
444
|
end
|
445
445
|
|
446
446
|
def update(success)
|
@@ -465,7 +465,7 @@ class ProgressTracker
|
|
465
465
|
private
|
466
466
|
|
467
467
|
def should_update?
|
468
|
-
now =
|
468
|
+
now = Familia.now
|
469
469
|
return false if (now - @last_update) < 0.5 # Update at most every 500ms
|
470
470
|
@last_update = now
|
471
471
|
true
|
@@ -474,7 +474,7 @@ class ProgressTracker
|
|
474
474
|
def show_ops_progress
|
475
475
|
percent = (@completed.to_f / @total_ops * 100).round(1)
|
476
476
|
success_rate = (@successful.to_f / @completed * 100).round(1) if @completed > 0
|
477
|
-
elapsed =
|
477
|
+
elapsed = Familia.now - @start_time
|
478
478
|
rate = (@completed / elapsed).round(1) if elapsed > 0
|
479
479
|
|
480
480
|
bar_width = 20
|
@@ -489,7 +489,7 @@ class ProgressTracker
|
|
489
489
|
end
|
490
490
|
|
491
491
|
def show_time_progress
|
492
|
-
elapsed =
|
492
|
+
elapsed = Familia.now - @start_time
|
493
493
|
success_rate = (@successful.to_f / @completed * 100).round(1) if @completed > 0
|
494
494
|
rate = (@completed / elapsed).round(1) if elapsed > 0
|
495
495
|
|
@@ -29,7 +29,7 @@ class StressTestRunner
|
|
29
29
|
def initialize(options = {})
|
30
30
|
@options = {
|
31
31
|
config_set: :moderate,
|
32
|
-
output_dir: "stress_test_results_#{
|
32
|
+
output_dir: "stress_test_results_#{Familia.now.strftime('%Y%m%d_%H%M%S')}",
|
33
33
|
threading_models: [:traditional, :thread_pool, :fiber],
|
34
34
|
operation_mixes: [:balanced, :read_heavy, :write_heavy],
|
35
35
|
generate_visualizations: true,
|
@@ -67,7 +67,7 @@ class StressTestRunner
|
|
67
67
|
puts "Total tests to run: #{total_tests}"
|
68
68
|
puts ""
|
69
69
|
|
70
|
-
start_time =
|
70
|
+
start_time = Familia.now
|
71
71
|
|
72
72
|
@config_set[:scenarios].each do |scenario|
|
73
73
|
puts "\n--- Testing Scenario: #{scenario} ---"
|
@@ -105,7 +105,7 @@ class StressTestRunner
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
108
|
-
duration =
|
108
|
+
duration = Familia.now - start_time
|
109
109
|
puts "\n" + "=" * 80
|
110
110
|
puts "ALL TESTS COMPLETED"
|
111
111
|
puts "Total duration: #{format_duration(duration)}"
|
@@ -206,7 +206,7 @@ class StressTestRunner
|
|
206
206
|
end
|
207
207
|
|
208
208
|
def save_test_results(config, metrics, model_info)
|
209
|
-
timestamp =
|
209
|
+
timestamp = Familia.now.strftime('%Y%m%d_%H%M%S_%L')
|
210
210
|
test_id = "#{config[:threading_model]}_#{config[:scenario]}_#{timestamp}"
|
211
211
|
|
212
212
|
# Export detailed CSV files
|
@@ -217,7 +217,7 @@ class StressTestRunner
|
|
217
217
|
|
218
218
|
# Save test configuration and results
|
219
219
|
test_data = {
|
220
|
-
timestamp:
|
220
|
+
timestamp: Familia.now,
|
221
221
|
config: config,
|
222
222
|
model_info: model_info,
|
223
223
|
summary: metrics.respond_to?(:detailed_summary) ? metrics.detailed_summary : metrics.summary
|
@@ -267,7 +267,7 @@ class StressTestRunner
|
|
267
267
|
<<~README
|
268
268
|
# Connection Pool Stress Test Results
|
269
269
|
|
270
|
-
Generated: #{
|
270
|
+
Generated: #{Familia.now}
|
271
271
|
Configuration: #{@options[:config_set]}
|
272
272
|
|
273
273
|
## Directory Structure
|
@@ -305,7 +305,7 @@ class StressTestRunner
|
|
305
305
|
summary = <<~SUMMARY
|
306
306
|
# Executive Summary - Connection Pool Stress Testing
|
307
307
|
|
308
|
-
**Generated**: #{
|
308
|
+
**Generated**: #{Familia.now}
|
309
309
|
**Test Configuration**: #{@options[:config_set]}
|
310
310
|
|
311
311
|
## Key Findings
|