mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-24 07:39:39 +02:00
* Realistic demo data for performance testing * Add note about performance testing * Fix bugbot issues * More realistic account values
349 lines
13 KiB
Ruby
349 lines
13 KiB
Ruby
# Performance testing scenario - high-volume data for load testing
|
|
#
|
|
# This scenario creates large volumes of realistic data to test application
|
|
# performance under load. Uses an efficient approach: generates one complete
|
|
# realistic family in Ruby, then uses SQL bulk operations to duplicate it
|
|
# 499 times for maximum performance. Ideal for:
|
|
# - Performance testing and benchmarking
|
|
# - Load testing database operations
|
|
# - UI performance testing with large datasets
|
|
# - Scalability validation at production scale
|
|
#
|
|
|
|
require "bcrypt"
|
|
|
|
class Demo::Scenarios::PerformanceTesting < Demo::BaseScenario
|
|
# Scenario characteristics and configuration
|
|
SCENARIO_NAME = "Performance Testing".freeze
|
|
PURPOSE = "High-volume data generation for performance testing and load validation".freeze
|
|
TARGET_FAMILIES = 500
|
|
TARGET_ACCOUNTS_PER_FAMILY = 29 # 3 credit cards, 5 checking, 2 savings, 10 investments, 2 properties+mortgages, 3 vehicles+2 loans, 4 other assets+liabilities
|
|
TARGET_TRANSACTIONS_PER_FAMILY = 200 # Reasonable volume for development performance testing
|
|
TARGET_TRANSFERS_PER_FAMILY = 10
|
|
SECURITIES_COUNT = 50 # Large number for investment account testing
|
|
INCLUDES_SECURITIES = true
|
|
INCLUDES_TRANSFERS = true
|
|
INCLUDES_RULES = true
|
|
|
|
# Override generate! to use our efficient bulk duplication approach
|
|
def generate!(families, **options)
|
|
puts "Creating performance test data for #{TARGET_FAMILIES} families using efficient bulk duplication..."
|
|
|
|
setup(**options) if respond_to?(:setup, true)
|
|
|
|
# Step 1: Create one complete realistic family
|
|
template_family = create_template_family!(families.first, **options)
|
|
|
|
# Step 2: Efficiently duplicate it 499 times using SQL
|
|
duplicate_family_data!(template_family, TARGET_FAMILIES - 1)
|
|
|
|
puts "Performance test data created successfully with #{TARGET_FAMILIES} families!"
|
|
end
|
|
|
|
private
|
|
|
|
# Load large number of securities before generating family data
|
|
def setup(**options)
|
|
@generators[:security_generator].load_securities!(count: SECURITIES_COUNT)
|
|
puts "#{SECURITIES_COUNT} securities loaded for performance testing"
|
|
end
|
|
|
|
# Create one complete, realistic family that will serve as our template
|
|
def create_template_family!(family_or_name, **options)
|
|
# Handle both Family object and family name string
|
|
family = if family_or_name.is_a?(Family)
|
|
family_or_name
|
|
else
|
|
Family.find_by(name: family_or_name)
|
|
end
|
|
|
|
unless family
|
|
raise "Template family '#{family_or_name}' not found. Ensure family creation happened first."
|
|
end
|
|
|
|
puts "Creating template family: #{family.name}..."
|
|
generate_family_data!(family, **options)
|
|
|
|
puts "Template family created with #{family.accounts.count} accounts and #{family.entries.count} entries"
|
|
family
|
|
end
|
|
|
|
# Efficiently duplicate the template family data using SQL bulk operations
|
|
def duplicate_family_data!(template_family, copies_needed)
|
|
puts "Duplicating template family #{copies_needed} times using efficient SQL operations..."
|
|
|
|
ActiveRecord::Base.transaction do
|
|
# Get all related data for the template family
|
|
template_data = extract_template_data(template_family)
|
|
|
|
# Create family records in batches
|
|
create_family_copies(template_family, copies_needed)
|
|
|
|
# Bulk duplicate all related data
|
|
duplicate_accounts_and_related_data(template_data, copies_needed)
|
|
end
|
|
|
|
puts "Successfully created #{copies_needed} family copies"
|
|
end
|
|
|
|
# Extract all data related to the template family for duplication
|
|
def extract_template_data(family)
|
|
{
|
|
accounts: family.accounts.includes(:accountable),
|
|
entries: family.entries.includes(:entryable),
|
|
categories: family.categories,
|
|
merchants: family.merchants,
|
|
tags: family.tags,
|
|
rules: family.rules,
|
|
holdings: family.holdings
|
|
}
|
|
end
|
|
|
|
# Create family and user records efficiently
|
|
def create_family_copies(template_family, count)
|
|
puts "Creating #{count} family records..."
|
|
|
|
families_data = []
|
|
users_data = []
|
|
password_digest = BCrypt::Password.create("password")
|
|
|
|
(2..count + 1).each do |i|
|
|
family_id = SecureRandom.uuid
|
|
family_name = "Performance Family #{i}"
|
|
|
|
families_data << {
|
|
id: family_id,
|
|
name: family_name,
|
|
currency: template_family.currency,
|
|
locale: template_family.locale,
|
|
country: template_family.country,
|
|
timezone: template_family.timezone,
|
|
date_format: template_family.date_format,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
|
|
# Create admin user
|
|
users_data << {
|
|
id: SecureRandom.uuid,
|
|
family_id: family_id,
|
|
email: "user#{i}@maybe.local",
|
|
first_name: "Demo",
|
|
last_name: "User",
|
|
role: "admin",
|
|
password_digest: password_digest,
|
|
onboarded_at: Time.current,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
|
|
# Create member user
|
|
users_data << {
|
|
id: SecureRandom.uuid,
|
|
family_id: family_id,
|
|
email: "member_user#{i}@maybe.local",
|
|
first_name: "Demo (member user)",
|
|
last_name: "User",
|
|
role: "member",
|
|
password_digest: password_digest,
|
|
onboarded_at: Time.current,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
end
|
|
|
|
# Bulk insert families and users
|
|
Family.insert_all(families_data)
|
|
User.insert_all(users_data)
|
|
|
|
puts "Created #{count} families and #{users_data.length} users"
|
|
end
|
|
|
|
# Efficiently duplicate accounts and all related data using SQL
|
|
def duplicate_accounts_and_related_data(template_data, count)
|
|
puts "Duplicating accounts and related data for #{count} families..."
|
|
|
|
new_families = Family.where("name LIKE 'Performance Family %'")
|
|
.where.not(id: template_data[:accounts].first&.family_id)
|
|
.limit(count)
|
|
|
|
new_families.find_each.with_index do |family, index|
|
|
duplicate_family_accounts_bulk(template_data, family)
|
|
puts "Completed family #{index + 1}/#{count}" if (index + 1) % 50 == 0
|
|
end
|
|
end
|
|
|
|
# Duplicate all accounts and related data for a single family using bulk operations
|
|
def duplicate_family_accounts_bulk(template_data, target_family)
|
|
return if template_data[:accounts].empty?
|
|
|
|
account_id_mapping = {}
|
|
|
|
# Create accounts one by one to handle accountables properly
|
|
template_data[:accounts].each do |template_account|
|
|
new_account = target_family.accounts.create!(
|
|
accountable: template_account.accountable.dup,
|
|
name: template_account.name,
|
|
balance: template_account.balance,
|
|
currency: template_account.currency,
|
|
subtype: template_account.subtype,
|
|
is_active: template_account.is_active
|
|
)
|
|
account_id_mapping[template_account.id] = new_account.id
|
|
end
|
|
|
|
# Bulk create other related data
|
|
create_bulk_categories(template_data[:categories], target_family)
|
|
create_bulk_entries_and_related(template_data, target_family, account_id_mapping)
|
|
rescue => e
|
|
puts "Error duplicating data for #{target_family.name}: #{e.message}"
|
|
# Continue with next family rather than failing completely
|
|
end
|
|
|
|
# Bulk create categories for a family
|
|
def create_bulk_categories(template_categories, target_family)
|
|
return if template_categories.empty?
|
|
|
|
# Create mapping from old category IDs to new category IDs
|
|
category_id_mapping = {}
|
|
|
|
# First pass: generate new IDs for all categories
|
|
template_categories.each do |template_category|
|
|
category_id_mapping[template_category.id] = SecureRandom.uuid
|
|
end
|
|
|
|
# Second pass: create category data with properly mapped parent_ids
|
|
categories_data = template_categories.map do |template_category|
|
|
# Map parent_id to the new family's category ID, or nil if no parent
|
|
new_parent_id = template_category.parent_id ? category_id_mapping[template_category.parent_id] : nil
|
|
|
|
{
|
|
id: category_id_mapping[template_category.id],
|
|
family_id: target_family.id,
|
|
name: template_category.name,
|
|
color: template_category.color,
|
|
classification: template_category.classification,
|
|
parent_id: new_parent_id,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
end
|
|
|
|
Category.insert_all(categories_data)
|
|
end
|
|
|
|
# Bulk create entries and related entryables
|
|
def create_bulk_entries_and_related(template_data, target_family, account_id_mapping)
|
|
return if template_data[:entries].empty?
|
|
|
|
entries_data = []
|
|
transactions_data = []
|
|
trades_data = []
|
|
|
|
template_data[:entries].each do |template_entry|
|
|
new_account_id = account_id_mapping[template_entry.account_id]
|
|
next unless new_account_id
|
|
|
|
new_entry_id = SecureRandom.uuid
|
|
new_entryable_id = SecureRandom.uuid
|
|
|
|
entries_data << {
|
|
id: new_entry_id,
|
|
account_id: new_account_id,
|
|
entryable_type: template_entry.entryable_type,
|
|
entryable_id: new_entryable_id,
|
|
name: template_entry.name,
|
|
date: template_entry.date,
|
|
amount: template_entry.amount,
|
|
currency: template_entry.currency,
|
|
notes: template_entry.notes,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
|
|
# Create entryable data based on type
|
|
case template_entry.entryable_type
|
|
when "Transaction"
|
|
transactions_data << {
|
|
id: new_entryable_id,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
when "Trade"
|
|
trades_data << {
|
|
id: new_entryable_id,
|
|
security_id: template_entry.entryable.security_id,
|
|
qty: template_entry.entryable.qty,
|
|
price: template_entry.entryable.price,
|
|
currency: template_entry.entryable.currency,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
end
|
|
end
|
|
|
|
# Bulk insert all data
|
|
Entry.insert_all(entries_data) if entries_data.any?
|
|
Transaction.insert_all(transactions_data) if transactions_data.any?
|
|
Trade.insert_all(trades_data) if trades_data.any?
|
|
end
|
|
|
|
# Generate high-volume family data for the template family
|
|
def generate_family_data!(family, **options)
|
|
create_foundational_data!(family)
|
|
create_high_volume_accounts!(family)
|
|
create_performance_transactions!(family)
|
|
create_performance_transfers!(family)
|
|
end
|
|
|
|
# Create rules, tags, categories and merchants for performance testing
|
|
def create_foundational_data!(family)
|
|
@generators[:rule_generator].create_tags!(family)
|
|
@generators[:rule_generator].create_categories!(family)
|
|
@generators[:rule_generator].create_merchants!(family)
|
|
@generators[:rule_generator].create_rules!(family)
|
|
puts " - Foundational data created (tags, categories, merchants, rules)"
|
|
end
|
|
|
|
# Create large numbers of accounts across all types for performance testing
|
|
def create_high_volume_accounts!(family)
|
|
@generators[:account_generator].create_credit_card_accounts!(family, count: 3)
|
|
puts " - 3 credit card accounts created"
|
|
|
|
@generators[:account_generator].create_checking_accounts!(family, count: 5)
|
|
puts " - 5 checking accounts created"
|
|
|
|
@generators[:account_generator].create_savings_accounts!(family, count: 2)
|
|
puts " - 2 savings accounts created"
|
|
|
|
@generators[:account_generator].create_investment_accounts!(family, count: 10)
|
|
puts " - 10 investment accounts created"
|
|
|
|
@generators[:account_generator].create_properties_and_mortgages!(family, count: 2)
|
|
puts " - 2 properties and mortgages created"
|
|
|
|
@generators[:account_generator].create_vehicles_and_loans!(family, vehicle_count: 3, loan_count: 2)
|
|
puts " - 3 vehicles and 2 loans created"
|
|
|
|
@generators[:account_generator].create_other_accounts!(family, asset_count: 4, liability_count: 4)
|
|
puts " - 4 other assets and 4 other liabilities created"
|
|
|
|
puts " - Total: #{TARGET_ACCOUNTS_PER_FAMILY} accounts created for performance testing"
|
|
end
|
|
|
|
# Create high-volume transactions for performance testing
|
|
def create_performance_transactions!(family)
|
|
@generators[:transaction_generator].create_performance_transactions!(family)
|
|
puts " - High-volume performance transactions created (~#{TARGET_TRANSACTIONS_PER_FAMILY} transactions)"
|
|
end
|
|
|
|
# Create multiple transfer cycles for performance testing
|
|
def create_performance_transfers!(family)
|
|
@generators[:transfer_generator].create_transfer_transactions!(family, count: TARGET_TRANSFERS_PER_FAMILY)
|
|
puts " - #{TARGET_TRANSFERS_PER_FAMILY} transfer transaction cycles created"
|
|
end
|
|
|
|
def scenario_name
|
|
SCENARIO_NAME
|
|
end
|
|
end
|