October 30, 2018
We use devise gem for authentication in one of our applications. This application provides an API which uses token authentication provided by the devise gem.
We were authenticating the user using auth token for every API call.
class Api::V1::BaseController < ApplicationController
before_action :authenticate_user_using_x_auth_token
before_action :authenticate_user!
def authenticate_user_using_x_auth_token
user_email = params[:email].presence || request.headers['X-Auth-Email']
auth_token = request.headers['X-Auth-Token'].presence
@user = user_email && User.find_by(email: user_email)
if @user && Devise.secure_compare(@user.authentication_token, auth_token)
sign_in @user, store: false
else
render_errors('Could not authenticate with the provided credentials', 401)
end
end
end
Everything was working smoothly initially, but we started noticing significant reduction in the response times during peak hours after a few months.
Because of the nature of the business, the application gets API calls for every user after every minute. Sometimes the application also get concurrent API calls for the same user. We noticed that in such cases, the users table was getting locked during the authentication process. This was resulting into cascading holdups and timeouts as it was affecting other API calls which were also accessing the users table.
After looking at the monitoring information,
we found that the problem was happening due to the trackable
module
of devise gem.
The trackable
module keeps track of the user by storing the sign in time, sign
in count and IP address information.
Following queries were running for every API call and were resulting into exclusive locks on
the users table.
UPDATE users SET last_sign_in_at = '2018-01-09 04:55:04',
current_sign_in_at = '2018-01-09 04:55:05',
sign_in_count = 323,
updated_at = '2018-01-09 04:55:05'
WHERE users.id = $1
To fix this issue, we decided to skip the user tracking for the API calls. We don't need to track the user as every call is stateless and every request authenticates the user.
Devise provides a hook to achieve this for certain requests through the environment of the request. As we were already using a separate base controller for API requests, it was easy to skip it for all API calls at once.
class Api::V1::BaseController < ApplicationController
before_action :skip_trackable
before_action :authenticate_user_using_x_auth_token
before_action :authenticate_user!
def skip_trackable
request.env['warden'].request.env['devise.skip_trackable'] = '1'
end
end
This fixed the issue of exclusive locks on the users table caused by the trackable module.
If this blog was helpful, check out our full blog archive.