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.
1class Api::V1::BaseController < ApplicationController 2 before_action :authenticate_user_using_x_auth_token 3 before_action :authenticate_user! 4 5 def authenticate_user_using_x_auth_token 6 user_email = params[:email].presence || request.headers['X-Auth-Email'] 7 auth_token = request.headers['X-Auth-Token'].presence 8 9 @user = user_email && User.find_by(email: user_email) 10 11 if @user && Devise.secure_compare(@user.authentication_token, auth_token) 12 sign_in @user, store: false 13 else 14 render_errors('Could not authenticate with the provided credentials', 401) 15 end 16 end 17end
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.
1UPDATE users SET last_sign_in_at = '2018-01-09 04:55:04', 2current_sign_in_at = '2018-01-09 04:55:05', 3sign_in_count = 323, 4updated_at = '2018-01-09 04:55:05' 5WHERE 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.
1class Api::V1::BaseController < ApplicationController 2 before_action :skip_trackable 3 before_action :authenticate_user_using_x_auth_token 4 before_action :authenticate_user! 5 6 def skip_trackable 7 request.env['warden'].request.env['devise.skip_trackable'] = '1' 8 end 9end
This fixed the issue of exclusive locks on the users table caused by the trackable module.