Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memoize key_provider from key or deterministic key_provider if any #51324

Merged
merged 1 commit into from Mar 14, 2024

Conversation

rosa
Copy link
Member

@rosa rosa commented Mar 13, 2024

Motivation / Background

We're in the process of bringing one of our apps up to date with main, and in the process of doing so, we realised our tests were taking about 5 times longer to run. We traced the slowdown back to #51019.

The memoization of Scheme#key_provider was removed completely in that PR because it prevented overriding the key_provider via with_encryption_context. For encrypted attributes where we either provide a key or declare them as deterministic, we have to derive a key to instantiate the provider every time we load them. This might happen hundreds of times per test, and ultimately means we call

ActiveSupport::KeyGenerator#generate_key

and

OpenSSL::KDF.pbkdf2_hmac

hundreds of times. This adds significant overhead per test.

In reality, what's overridden by with_encryption_context is the value used as default provider, that is, ActiveRecord::Encryption.key_provider. This is only used in Scheme#key_provider if the scheme doesn't already have either a key_provider passed directly, or a key, or is deterministic, because it's called via default_key_provider here:

def key_provider
@key_provider_param || build_key_provider || default_key_provider
end

def default_key_provider
ActiveRecord::Encryption.key_provider
end

However, build_key_provider takes precedence, and it's very expensive to do:

def build_key_provider
return DerivedSecretKeyProvider.new(@key) if @key.present?
if @deterministic
DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
end
end

Neither @key nor @deterministic can be overridden, so this PR memoizes them if we have to build them.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Changes that are unrelated should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

cc @jhawthorn @kstevens715

The memoization of Scheme#key_provider was removed completely in
rails#51019 because it prevented
overriding the `key_provider` via `with_encryption_context`. However,
this also made our tests for the HEY app about 5 times slower. We traced
this down to all attributes where we either provide a key or declare them
as deterministic and have to derive a key to instantiate the provider
every time we load them. This might happen hundreds of times per test,
and ultimately, we call
```
ActiveSupport::KeyGenerator#generate_key
```
and
```
OpenSSL::KDF.pbkdf2_hmac
```
hundreds of times. This adds significant overhead per test.

In reality, what's overridden bv `with_encryption_context` is the value
used as default provider, that is, `ActiveRecord::Encryption.key_provider`.
This is only used in `Scheme#key_provider` if the scheme doesn't already have
either a `key_provider` passed directly, or a `key`, or is `deterministic`.
The `key_provider` passed directly is already memoized simply by having it
stored as is. Let's memoize the other two, in order, so we save all those
extra calls to derive the same keys again and again.
@rosa rosa force-pushed the memoize-invariable-providers-in-scheme branch from 7927f12 to 4a0b2a0 Compare March 13, 2024 21:41
@jhawthorn
Copy link
Member

Neither @key nor @deterministic can be overridden, so this PR memoizes them if we have to build them.

👍 If that's true this all makes sense to me. Let's make sure to backport this to 7-1-stable as well (as the previous fix was)

@jhawthorn jhawthorn merged commit 93b89b1 into rails:main Mar 14, 2024
3 of 4 checks passed
jhawthorn added a commit that referenced this pull request Mar 14, 2024
…cheme

Memoize `key_provider` from `key` or deterministic `key_provider` if any
@jhawthorn
Copy link
Member

Thanks Rosa! Also merged into 7-1-stable in 71bb84b

@jorgemanrubia
Copy link
Contributor

Amazing work here @rosa 👏

@rosa
Copy link
Member Author

rosa commented Mar 14, 2024

Yay! Thank you so much, @jhawthorn and @jorgemanrubia ❤️ 🙏

@rosa rosa deleted the memoize-invariable-providers-in-scheme branch March 14, 2024 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants