-
Notifications
You must be signed in to change notification settings - Fork 3
Cache invalidation during database transactions
Donald Dong edited this page Apr 23, 2021
·
1 revision
Suppose we have CachedUser
implemented like this:
def fetch
Rails.cache.fetch("user:#{user_id}") do
User.find(user_id) # load from the database on cache-miss
end
end
then we will run into issues when using it during database transactions.
If the cache is invalidated immediately after a not-yet-committed write, the following cache read would fill the cache with data that could be rolled back later.
user # actual: #<User id: 1, name: "Old Name">
CachedUser.new(user.id).fetch # cache: #<User id: 1, name: "Old Name">
User.transaction do
user.update!(name: "✨ New Name ✨")
CachedUser.new(user.id).invalidate # mid-transaction invalidation 🗑
CachedUser.new(user.id).fetch # cache: #<User id: 1, name: "✨ New Name
raise ActiveRecord::Rollback
end
User.find(user.id) # actual: #<User id: 1, name: "Old Name">
CachedUser.new(user.id).fetch # cache: #<User id: 1, name: "✨ New Name ✨">
If the cache is invalidated after committing the transaction, the following cache read would use the stale data from the cache as if the changes did not occur.
user # actual: #<User id: 1, name: "Old Name">
CachedUser.new(user.id).fetch # cache: #<User id: 1, name: "Old Name">
User.transaction do
user.update!(name: "✨ New Name ✨")
User.find(user.id) # actual: #<User id: 1, name: "✨ New Name ✨">
CachedUser.new(user.id).fetch # cache: #<User id: 1, name: "Old Name">
raise ActiveRecord::Rollback
end
CachedUser.new(user.id).invalidate # post-transaction invalidation 🗑
RedisMemo resolves this issue by implementing MVCC.