BigBinary Blog

We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.

Skipping devise trackable module for API calls

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
      render_errors('Could not authenticate with the provided credentials', 401)

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 = $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'

This fixed the issue of exclusive locks on the users table caused by the trackable module.

Prathamesh Sonpatki in Rails
30, 2018

You might also like

Rails 6.1 adds support for PostgreSQL interval data type

Rails 6.1 allows per environment configuration support for Active Storage

Rails 6.1 adds support for belongs_to to has_many inversing

Rails 6.1 adds strict_loading to warn lazy loading associations

Rails 6.1 adds where.associated to check association presence

Subscribe to our newsletter