When not to use Memoization in Ruby on Rails

Memoization is a wonderful concept in programming world. It helps in writing clean code which execute faster.

Example:

In above code, slow_method will cache perform_slow_method in @result variable, therefore perform_slow_method will execute only once.

So, if memoization is wonderful then why not to use it always ? that’s the question I am going to answer in this post.

Memoization should be avoided if result of memoized function is going to change over time and your business logic relies on latest value. For example if perform_slow_method is doing DB call to fetch data from database and your business logic needs latest data then using memoization may give you cached data which is not desired.

Is it safe to use Memoization ?

I have read multiple times that ||= operator is not thread safe because it is performing 2 operations, which is correct. Does it mean that memoization is not thread safe ? Answer to this question is Yes & No 😉. Actually it depends on scenario where we are using it.

If you are wondering how performing 2 operations can make a code not thread safe, let me explain it to you first & then we will see scenarios where memoization is thread safe & not thread safe.

If multiple threads are executing then they will read & write value of x together which can lead to inconsistent value of x. Atomic operations can solve this

Luca Guidi has written a nice blog to explain it

If we design write operations in a way that while they’re running, other threads can’t read nor alter the state we’re modifying, that change is thread safe.

We can use Mutex to make it Atomic

Now, let’s see scenarios where memoization is thread safe & not thread safe in rails code.

There are 2 scenarios when memoization is not thread safe

  1. Using class variable or class instance variable to cache result
  2. Spawning new thread explicitly in your code

Note: Do not confuse class instance variable with object instance variable

Using class variable or class instance variable to cache result

Class variable and class instance variable are shared by multiple thread therefore using it for memoization will create inconsistent state

Example:

Output of this program is:

But sometime output is:

Reason for this behaviour: Both threads are trying to manipulate config variable. When second thread started execution, its possible that first thread has already set config to {:global=>0} therefore merge operation didn’t happened for second thread.

Rails had similar issue which is resolved in this PR

Spawning new thread explicitly in your code

If you are spawning thread in your code then you need to be careful while sharing object & using memoization.

Example:

Output of this code is:

Sometimes output will be:

When spawning new threads, both threads are sharing appobject, as we don’t know order of execution therefore it will lead to unexpected result.

Puma or any other multi threaded application server process requests in separate threads therefore we don’t see this problem when using memoization in Rails application, provided we are not spawning thread and sharing objects between threads in our application.

Conclusion

  1. Memoization should be avoided when:
  • Memoized function is going to change over time and your business logic relies on latest value
  • Spawning new thread in application

2. Class variable and class instance variable should not be used for caching.

Principal Engineer @joshsoftware

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store