mirror of https://github.com/rails/rails
Delete `EncryptionPerformanceTest`
On my machine, running the whole Active Record test suite takes `88` seconds, and `40` of these are spent in encryption tests. Some of them also happen to flake because of random blips. I appreciate the care that has been put into ensuring the overhead of encrption was reasonable, but I don't think these tests justify their cost.
This commit is contained in:
parent
ed2e77d691
commit
3e7ba58439
2
Gemfile
2
Gemfile
|
@ -143,8 +143,6 @@ group :test do
|
||||||
gem "debug", ">= 1.1.0", require: false
|
gem "debug", ">= 1.1.0", require: false
|
||||||
end
|
end
|
||||||
|
|
||||||
gem "benchmark-ips"
|
|
||||||
|
|
||||||
# Needed for Railties tests because it is included in generated apps.
|
# Needed for Railties tests because it is included in generated apps.
|
||||||
gem "brakeman"
|
gem "brakeman"
|
||||||
end
|
end
|
||||||
|
|
|
@ -159,7 +159,6 @@ GEM
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
beaneater (1.1.3)
|
beaneater (1.1.3)
|
||||||
benchmark-ips (2.13.0)
|
|
||||||
bigdecimal (3.1.5)
|
bigdecimal (3.1.5)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.17.0)
|
bootsnap (1.17.0)
|
||||||
|
@ -596,7 +595,6 @@ DEPENDENCIES
|
||||||
azure-storage-blob (~> 2.0)
|
azure-storage-blob (~> 2.0)
|
||||||
backburner
|
backburner
|
||||||
bcrypt (~> 3.1.11)
|
bcrypt (~> 3.1.11)
|
||||||
benchmark-ips
|
|
||||||
bootsnap (>= 1.4.4)
|
bootsnap (>= 1.4.4)
|
||||||
brakeman
|
brakeman
|
||||||
capybara (>= 3.39)
|
capybara (>= 3.39)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cases/helper"
|
require "cases/helper"
|
||||||
require "benchmark/ips"
|
|
||||||
|
|
||||||
class ActiveRecord::Fixture
|
class ActiveRecord::Fixture
|
||||||
prepend ActiveRecord::Encryption::EncryptedFixtures
|
prepend ActiveRecord::Encryption::EncryptedFixtures
|
||||||
|
@ -111,45 +110,6 @@ module ActiveRecord::Encryption
|
||||||
attribute_type.key_provider.keys = original_keys
|
attribute_type.key_provider.keys = original_keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module PerformanceHelpers
|
|
||||||
BENCHMARK_DURATION = 1
|
|
||||||
BENCHMARK_WARMUP = 1
|
|
||||||
BASELINE_LABEL = "Baseline"
|
|
||||||
CODE_TO_TEST_LABEL = "Code"
|
|
||||||
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# baseline = -> { <some baseline code> }
|
|
||||||
#
|
|
||||||
# assert_slower_by_at_most 2, baseline: baseline do
|
|
||||||
# <the code you want to compare against the baseline>
|
|
||||||
# end
|
|
||||||
def assert_slower_by_at_most(threshold_factor, baseline:, baseline_label: BASELINE_LABEL, code_to_test_label: CODE_TO_TEST_LABEL, duration: BENCHMARK_DURATION, quiet: true, &block_to_test)
|
|
||||||
GC.start
|
|
||||||
|
|
||||||
result = nil
|
|
||||||
output, error = capture_io do
|
|
||||||
result = Benchmark.ips do |x|
|
|
||||||
x.config(time: duration, warmup: BENCHMARK_WARMUP)
|
|
||||||
x.report(code_to_test_label, &block_to_test)
|
|
||||||
x.report(baseline_label, &baseline)
|
|
||||||
x.compare!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
baseline_result = result.entries.find { |entry| entry.label == baseline_label }
|
|
||||||
code_to_test_result = result.entries.find { |entry| entry.label == code_to_test_label }
|
|
||||||
|
|
||||||
times_slower = baseline_result.ips / code_to_test_result.ips
|
|
||||||
|
|
||||||
if !quiet || times_slower >= threshold_factor
|
|
||||||
puts "#{output}#{error}"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert times_slower < threshold_factor, "Expecting #{threshold_factor} times slower at most, but got #{times_slower} times slower"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# We eager load encrypted attribute types as they are declared, so that they pick up the
|
# We eager load encrypted attribute types as they are declared, so that they pick up the
|
||||||
|
@ -163,7 +123,7 @@ ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_na
|
||||||
end
|
end
|
||||||
|
|
||||||
class ActiveRecord::EncryptionTestCase < ActiveRecord::TestCase
|
class ActiveRecord::EncryptionTestCase < ActiveRecord::TestCase
|
||||||
include ActiveRecord::Encryption::EncryptionHelpers, ActiveRecord::Encryption::PerformanceHelpers
|
include ActiveRecord::Encryption::EncryptionHelpers
|
||||||
|
|
||||||
ENCRYPTION_PROPERTIES_TO_RESET = {
|
ENCRYPTION_PROPERTIES_TO_RESET = {
|
||||||
config: %i[ primary_key deterministic_key key_derivation_salt store_key_references hash_digest_class
|
config: %i[ primary_key deterministic_key key_derivation_salt store_key_references hash_digest_class
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "cases/encryption/helper"
|
|
||||||
require "models/book_encrypted"
|
|
||||||
require "models/post_encrypted"
|
|
||||||
|
|
||||||
class ActiveRecord::Encryption::EncryptionPerformanceTest < ActiveRecord::EncryptionTestCase
|
|
||||||
fixtures :encrypted_books, :posts
|
|
||||||
|
|
||||||
setup do
|
|
||||||
ActiveRecord::Encryption.config.support_unencrypted_data = true
|
|
||||||
end
|
|
||||||
|
|
||||||
test "performance when saving records" do
|
|
||||||
baseline = -> { create_post_without_encryption }
|
|
||||||
|
|
||||||
assert_slower_by_at_most 1.4, baseline: baseline do
|
|
||||||
create_post_with_encryption
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "reading an encrypted attribute multiple times is as fast as reading a regular attribute" do
|
|
||||||
unencrypted_post = create_post_without_encryption
|
|
||||||
baseline = -> { unencrypted_post.reload.title }
|
|
||||||
|
|
||||||
encrypted_post = create_post_with_encryption
|
|
||||||
assert_slower_by_at_most 1.2, baseline: baseline, duration: 3 do
|
|
||||||
encrypted_post.reload.title
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def create_post_without_encryption
|
|
||||||
ActiveRecord::Encryption.without_encryption { create_post_with_encryption }
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_post_with_encryption
|
|
||||||
EncryptedPost.create!\
|
|
||||||
title: "the Starfleet is here!",
|
|
||||||
body: "<p>the Starfleet is here, we are safe now!</p>"
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,53 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "cases/encryption/helper"
|
|
||||||
require "models/book_encrypted"
|
|
||||||
|
|
||||||
class ActiveRecord::Encryption::EnvelopeEncryptionPerformanceTest < ActiveRecord::EncryptionTestCase
|
|
||||||
fixtures :encrypted_books
|
|
||||||
|
|
||||||
setup do
|
|
||||||
ActiveRecord::Encryption.config.support_unencrypted_data = true
|
|
||||||
@envelope_encryption_key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
|
|
||||||
end
|
|
||||||
|
|
||||||
test "performance when saving records" do
|
|
||||||
baseline = -> { create_book_without_encryption }
|
|
||||||
|
|
||||||
assert_slower_by_at_most 1.4, baseline: baseline do
|
|
||||||
with_envelope_encryption do
|
|
||||||
create_book
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "reading an encrypted attribute multiple times is as fast as reading a regular attribute" do
|
|
||||||
with_envelope_encryption do
|
|
||||||
baseline = -> { encrypted_books(:awdr).created_at }
|
|
||||||
book = create_book
|
|
||||||
assert_slower_by_at_most 1.05, baseline: baseline, duration: 3 do
|
|
||||||
book.name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def create_book_without_encryption
|
|
||||||
ActiveRecord::Encryption.without_encryption { create_book }
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_book
|
|
||||||
EncryptedBook.create! name: "Dune"
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypt_unencrypted_book
|
|
||||||
book = create_book_without_encryption
|
|
||||||
with_envelope_encryption do
|
|
||||||
book.encrypt
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_envelope_encryption(&block)
|
|
||||||
with_key_provider @envelope_encryption_key_provider, &block
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "cases/encryption/helper"
|
|
||||||
require "models/book_encrypted"
|
|
||||||
|
|
||||||
class ActiveRecord::Encryption::ExtendedDeterministicQueriesPerformanceTest < ActiveRecord::EncryptionTestCase
|
|
||||||
test "finding with prepared statement caching by deterministically encrypted columns" do
|
|
||||||
baseline = -> { EncryptedBook.find_by(format: "paperback") } # not encrypted
|
|
||||||
|
|
||||||
assert_slower_by_at_most 1.7, baseline: baseline, duration: 2 do
|
|
||||||
EncryptedBook.find_by(name: "Agile Web Development with Rails") # encrypted, deterministic
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "finding without prepared statement caching by encrypted columns (deterministic)" do
|
|
||||||
baseline = -> { EncryptedBook.where("id > 0").find_by(format: "paperback") } # not encrypted
|
|
||||||
|
|
||||||
# Overhead is 1.1 with SQL
|
|
||||||
assert_slower_by_at_most 1.3, baseline: baseline, duration: 2 do
|
|
||||||
EncryptedBook.where("id > 0").find_by(name: "Agile Web Development with Rails") # encrypted, deterministic
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,70 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "cases/encryption/helper"
|
|
||||||
|
|
||||||
class ActiveRecord::Encryption::StoragePerformanceTest < ActiveRecord::EncryptionTestCase
|
|
||||||
test "storage overload without storing keys is acceptable" do
|
|
||||||
assert_storage_performance size: 2, overload_less_than: 47
|
|
||||||
assert_storage_performance size: 50, overload_less_than: 4
|
|
||||||
assert_storage_performance size: 255, overload_less_than: 1.6
|
|
||||||
assert_storage_performance size: 1.kilobyte, overload_less_than: 1.15
|
|
||||||
|
|
||||||
[500.kilobytes, 1.megabyte, 10.megabyte].each do |size|
|
|
||||||
assert_storage_performance size: size, overload_less_than: 1.03
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "storage overload storing keys is acceptable for DerivedSecretKeyProvider" do
|
|
||||||
ActiveRecord::Encryption.config.store_key_references = true
|
|
||||||
|
|
||||||
ActiveRecord::Encryption.with_encryption_context key_provider: ActiveRecord::Encryption::DerivedSecretKeyProvider.new("some secret") do
|
|
||||||
assert_storage_performance size: 2, overload_less_than: 54
|
|
||||||
assert_storage_performance size: 50, overload_less_than: 3.5
|
|
||||||
assert_storage_performance size: 255, overload_less_than: 1.64
|
|
||||||
assert_storage_performance size: 1.kilobyte, overload_less_than: 1.18
|
|
||||||
|
|
||||||
[500.kilobytes, 1.megabyte, 10.megabyte].each do |size|
|
|
||||||
assert_storage_performance size: size, overload_less_than: 1.03
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "storage overload storing keys is acceptable for EnvelopeEncryptionKeyProvider" do
|
|
||||||
ActiveRecord::Encryption.config.store_key_references = true
|
|
||||||
|
|
||||||
with_envelope_encryption do
|
|
||||||
assert_storage_performance size: 2, overload_less_than: 126
|
|
||||||
assert_storage_performance size: 50, overload_less_than: 6.28
|
|
||||||
assert_storage_performance size: 255, overload_less_than: 2.3
|
|
||||||
assert_storage_performance size: 1.kilobyte, overload_less_than: 1.3
|
|
||||||
|
|
||||||
[500.kilobytes, 1.megabyte, 10.megabyte].each do |size|
|
|
||||||
assert_storage_performance size: size, overload_less_than: 1.015
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def assert_storage_performance(size:, overload_less_than:, quiet: true)
|
|
||||||
clear_content = SecureRandom.urlsafe_base64(size).first(size) # .alphanumeric is very slow for large sizes
|
|
||||||
encrypted_content = encryptor.encrypt(clear_content)
|
|
||||||
|
|
||||||
overload_factor = encrypted_content.bytesize.to_f / clear_content.bytesize
|
|
||||||
|
|
||||||
if !quiet || overload_factor > overload_less_than
|
|
||||||
puts "#{clear_content.bytesize}; #{encrypted_content.bytesize}; #{(encrypted_content.bytesize / clear_content.bytesize.to_f)}"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert\
|
|
||||||
overload_factor <= overload_less_than,
|
|
||||||
"Expecting a storage overload of #{overload_less_than} at most for #{size} bytes, but got #{overload_factor} instead"
|
|
||||||
end
|
|
||||||
|
|
||||||
def encryptor
|
|
||||||
@encryptor ||= ActiveRecord::Encryption::Encryptor.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def cipher
|
|
||||||
@cipher ||= ActiveRecord::Encryption::Cipher.new
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in New Issue