Techioz Blog

Rails API コントローラーの最適化によるパフォーマンスの向上

概要

module Api
  module V1
    class AngularDashboardApisController < BaseController
      include RolesHelper
      before_action :verify_auth_token
      before_action -> { verify_user_permission(2) }

  def dashboard_data_a
    @user = User.find_by(id: params[:user_id])

    unless @user
      return json_response('Bad Request', false, [], :bad_request)
    end

    get_con_tech

    if !params[:from_date].present? && !params[:to_date].present?
      params[:from_date] = DateTime.now.beginning_of_month.to_date
      params[:to_date] = Date.today
    else
      params[:from_date] = params[:from_date].to_date
      params[:to_date] = params[:to_date].to_date
    end

    all_years = Lead.pluck(:created_at).map { |x| x.year }.uniq
    
    check_role(@user, params)
    filter_by_scope
    set_old_and_new_country_tech_hash_A_and_B
    graph_data
    lead_user_summary

    json_success_send_dashboard_graph_response('Dashboard-A', true,@countries_A, @countries_B, @countries_C,@country_total_A, @country_total_B,@country_total_C, @country_tech_array_hash_A, @country_tech_array_hash_B, @country_tech_array_hash_C,Hash[@technology_hash_A.sort_by{|k, v| v}].keys, Hash[@technology_hash_B.sort_by{|k, v| v}].keys, Hash[@technology_hash_C.sort_by{|k, v| v}].keys,@graphdata_lst, @Summary,all_years, :ok)
  end
  
  def graph_data
    graph_lead = sort_by_date(@country_tech_graph)
    @graphdata_map = Hash.new(0) # Initialize a default value of 0 for missing keys
    @graphdata_lst = []
  
    if params[:mode] == 'month' || params[:mode] == 'day' || params[:mode] == 'week'
      graph_lead.each do |lead|
        date_key = case params[:mode]
                   when 'month'
                     month_map[lead.date.month.to_s]
                   when 'day', 'week'
                     lead.date.to_date
                   end
  
        @graphdata_map[date_key] += 1
      end
    end
  
    @graphdata_map.each do |key, value|
      @graphdata_lst.push({ name: "#{key}", value: "#{value}", tooltip: "#{key}" })
    end
  end      

  def filter_by_scope
    @country_tech_hash_A, @country_tech_hash_A_graph, @country_tech_hash_id_A = filter_and_group_leads(@technology_hash_A, params)
    @country_tech_hash_B, @country_tech_hash_B_graph, @country_tech_hash_id_B = filter_and_group_leads(@technology_hash_B, params)
    @country_tech_hash_C, @country_tech_hash_C_graph, @country_tech_hash_id_C = filter_and_group_leads(@technology_hash_C, params)

    @country_tech_graph = @country_tech_hash_A + @country_tech_hash_B + @country_tech_hash_C

    @country_tech_hash_A = @country_tech_hash_A_graph
    @country_tech_hash_B = @country_tech_hash_B_graph
    @country_tech_hash_C = @country_tech_hash_C_graph
  end

  def set_old_and_new_country_tech_hash_A_and_B
    @new_country_tech_hash_id_A = {}
    @country_tech_hash_id_A.each do |key,value|
      if key[0] == nil
        key[0] = 'unknown'
      end
      k=key[0]+"", ""+key[1]
      @new_country_tech_hash_id_A[key[2]]=k
    end
    @new_country_tech_hash_id_A = @new_country_tech_hash_id_A.each_with_object({}) { |(k,v),g| (g[v] ||= []) << k }

    @country_tech_array_hash_A = {}
    @country_tech_array_A = []
    @countries_A = Set.new
    @countries_B = Set.new
    @countries_C = Set.new
    index = 0
    @klass = Klass.find_by(name: "Lead")
    @fields = @user.fields_for_table_with_order1(klass: @klass)
    @country_tech_hash_A.each do |key,value|
      array = []
      @countries_A.add(key[0])
      ids = @new_country_tech_hash_id_A[[key[0],key[1]]]
      @leads = Lead.where('id in (?)', ids).select(@fields.pluck(:name))
      if @country_tech_array_hash_A.key? (key[1])
        @country_tech_array_hash_A[key[1]] = @country_tech_array_hash_A[key[1]].push(array.push(key[0],value,@leads))
      else
        @country_tech_array_A = []
        @country_tech_array_hash_A[key[1]] = @country_tech_array_A.push(array.push(key[0],value,@leads))
      end
      @country_hash_A[key[0]] = index
      index = index + 1
      if @country_total_A.key? (key[0])
        @country_total_A[key[0]] = @country_total_A[key[0]] + value
      else
        @country_total_A[key[0]] =  value
      end
    end

    @new_country_tech_hash_id_B = {}
    @country_tech_hash_id_B.each do |key,value|
      if key[0] == nil
        key[0] = 'unknown'
      end
      k=key[0]+"", ""+key[1]
      @new_country_tech_hash_id_B[key[2]]=k
    end
    @new_country_tech_hash_id_B = @new_country_tech_hash_id_B.each_with_object({}) { |(k,v),g| (g[v] ||= []) << k }
    @country_tech_array_hash_B = {}
    @country_tech_array_B = []
    @country_tech_hash_B.each do |key,value|
      array = []
      @countries_B.add(key[0])
      ids = @new_country_tech_hash_id_B[[key[0],key[1]]]
      @leads = Lead.where('id in (?)', ids).select(@fields.pluck(:name))
      if @country_tech_array_hash_B.key? (key[1])
        @country_tech_array_hash_B[key[1]] = @country_tech_array_hash_B[key[1]].push(array.push(key[0],value,@leads))
      else
        @country_tech_array_B = []
        @country_tech_array_hash_B[key[1]] = @country_tech_array_B.push(array.push(key[0],value,@leads))
      end
      @country_hash_B[key[0]] = index
      index = index + 1
      if @country_total_B.key? (key[0])
        @country_total_B[key[0]] = @country_total_B[key[0]] + value
      else
        @country_total_B[key[0]] =  value
      end
    end

    @new_country_tech_hash_id_C = {}
    @country_tech_hash_id_C.each do |key,value|
      if key[0] == nil
        key[0] = 'unknown'
      end
      k=key[0]+"", ""+key[1]
      @new_country_tech_hash_id_C[key[2]]=k
    end
    @new_country_tech_hash_id_C = @new_country_tech_hash_id_C.each_with_object({}) { |(k,v),g| (g[v] ||= []) << k }
    @country_tech_array_hash_C = {}
    @country_tech_array_C = []
    @country_tech_hash_C.each do |key,value|
      array = []
      @countries_C.add(key[0])
      ids = @new_country_tech_hash_id_C[[key[0],key[1]]]
      @leads = Lead.where('id in (?)', ids).select(@fields.pluck(:name))
      if @country_tech_array_hash_C.key? (key[1])
        @country_tech_array_hash_C[key[1]] = @country_tech_array_hash_C[key[1]].push(array.push(key[0],value,@leads))
      else
        @country_tech_array_C = []
        @country_tech_array_hash_C[key[1]] = @country_tech_array_C.push(array.push(key[0],value,@leads))
      end
      @country_hash_C[key[0]] = index
      index = index + 1
      if @country_total_C.key? (key[0])
        @country_total_C[key[0]] = @country_total_C[key[0]] + value
      else
        @country_total_C[key[0]] =  value
      end
    end
  end

  def get_con_tech
    @country_hash_A = {}
    @country_hash_B = {}
    @country_hash_C = {}
    @technology_hash_A = {}
    @technology_hash_B = {}
    @technology_hash_C = {}

    @technology_A = CountryTechnology.where(group:'A',country_technology:'technology')
    @technology_B = CountryTechnology.where(group:'B',country_technology:'technology')
    @technology_C = CountryTechnology.where(group:'C',country_technology:'technology')

    @technology_A.each do |technology|
      @technology_hash_A[technology.name] = technology.position
    end

    @technology_B.each do |technology|
      @technology_hash_B[technology.name] = technology.position
    end

    @technology_C.each do |technology|
      @technology_hash_C[technology.name] = technology.position
    end
      @country_total_A = Hash[@country_hash_A.sort_by{|k, v| v}]
      @country_total_B = Hash[@country_hash_B.sort_by{|k, v| v}]
      @country_total_C = Hash[@country_hash_C.sort_by{|k, v| v}]
      @country_total_A.each { |k, v| @country_total_A[k] = 0 }
      @country_total_B.each { |k, v| @country_total_B[k] = 0 }
      @country_total_C.each { |k, v| @country_total_C[k] = 0 }
  end

  def lead_user_summary
    @LeadgroupByUser = Lead.filter_by_from_to_date_tech(params[:from_date], params[:to_date], @technology_hash_A.keys + @technology_hash_B.keys + @technology_hash_C.keys)
  
    filters = {
      source: params[:source],
      is_closed: params[:is_closed],
      live_chat_handle_name: params[:live_chat_handle_name],
      if_paid: params[:if_paid],
      if_organic: params[:if_organic],
      supportdesk: params[:supportdesk],
      terminate_reasons: params[:terminate_reasons],
      device: params[:device],
      user_id: params[:assign_to],
      technology: params[:technology]
    }
  
    filters.each do |filter_name, filter_value|
      @LeadgroupByUser = @LeadgroupByUser.send("filter_by_#{filter_name}", filter_value) if filter_value.present? && filter_value != "All"
    end
  
    @Summaryhash = @LeadgroupByUser.group(:user_id).count
    @LeadgroupByUser1 = @LeadgroupByUser.filter_by_is_closed("Yes")
    @Summaryhash1 = @LeadgroupByUser1.group(:user_id).count
    @Summary = []
    @klass = Klass.find_by(name: "Lead")
    @fields = @user.fields_for_table_with_order1(klass: @klass)
  
    @Summaryhash.each do |key, value|
      total_closed_count = @Summaryhash1[key].present? ? @Summaryhash1[key] : 0
      @assignedLeads1 = @LeadgroupByUser.where(user_id: key).select(:id, @fields.pluck(:name))
      @Summary.push({'key': key, 'total_assigned_count': value, 'total_closed_count': total_closed_count, 'assigned_leads': @assignedLeads1, 'labels': @fields.pluck(:name)})
    end
  end

  private

  def sort_by_date(arr)
    arr.sort_by { |h| h["date"].to_date.to_s.split('-') }
  end

  def filter_and_group_leads(technology_hash, params)
    filtered_leads = Lead.filter_by_from_to_date_tech(params[:from_date], params[:to_date], technology_hash.keys)
    filtered_leads = filtered_leads.filter_by_source(params[:source]) if params[:source].present? && params[:source] != "All"
    filtered_leads = filtered_leads.filter_by_is_closed(params[:is_closed]) if params[:is_closed].present? && params[:is_closed] != "All"
    filtered_leads = filtered_leads.filter_by_livechathandle(params[:live_chat_handle_name]) if params[:live_chat_handle_name].present? && params[:live_chat_handle_name] != "All"
    filtered_leads = filtered_leads.filter_by_if_paid(params[:if_paid]) if params[:if_paid].present? && params[:if_paid] != "All"
    filtered_leads = filtered_leads.filter_by_if_organic(params[:if_organic]) if params[:if_organic].present? && params[:if_organic] != "All"
    filtered_leads = filtered_leads.filter_by_supportdesk(params[:supportdesk]) if params[:supportdesk].present? && params[:supportdesk] != "All"
    filtered_leads = filtered_leads.filter_by_terminate_reasons(params[:terminate_reasons]) if params[:terminate_reasons].present? && params[:terminate_reasons] != "All"
    filtered_leads = filtered_leads.filter_by_device(params[:device]) if params[:device].present? && params[:device] != "All"
    filtered_leads = filtered_leads.filter_by_user_id(params[:assign_to]) if params[:assign_to].present? && params[:assign_to] != "All"
    filtered_leads = filtered_leads.filter_by_technology(params[:technology]) if params[:technology].present? && params[:technology] != "All"
  
    graph_data = filtered_leads.group(:country, :technology).count
    country_tech_hash_id = filtered_leads.group(:country, :technology, :id).count

    return filtered_leads, graph_data, country_tech_hash_id
  end
end
end
end

データベースからデータを取得する Rails API コントローラーを使用していますが、パフォーマンスの問題が発生しています。 API が同じデータを取得するのにかかる時間は毎回異なります。パフォーマンスを向上させるためにコードを最適化するためのアドバイスを探しています。

パフォーマンスを向上させるためにコードをリファクタリングすることで、dashboard_data_a メソッドの最適化を試みました。具体的には、データベース クエリをリファクタリングし、頻繁にアクセスされるデータのキャッシュを追加しました。

また、フィルタリング用の共通関数 filter_and_group_leads() も作成しましたが、これでは時間の複雑さは軽減されません。そのため、これらの最適化により、特にダッシュボードのデータを取得するリクエストの応答時間の大幅な短縮につながると期待していました。 A.ただし、若干の改善はありましたが、応答時間にはまだ大きなばらつきがあり、さらに改善の余地があると考えています。

解決策

コーディングスタイルが冗長すぎます。

サンディ・メッツについて聞いたことがありますか?彼女はコーディング スタイルについて詳しく研究しています。彼女はこう言います。メソッドには最大 5 行が必要です。したがって、メソッドをより小さな理解しやすいチャンクに分割することが、ある程度重要になります。

set_old_and_new_country_tech_hash_A_and_B メソッドには 98 行あり、発生している問題、特にパフォーマンスの問題を調べて特定するにはあまりにも奇妙です。もう一度これに取り組み、あなたの質問を編集します。私のコントローラーは、白い線を含む全長がこれの半分のサイズです。したがって、より小さなメソッドを作成することは、パフォーマンスの問題を特定するのにも役立ちます。

コードベースのパフォーマンスを支援することは、現状ではやや不可能です。実際、これは私が人生で見た中で最も長いコントローラー アクションであり、私は Ruby On Rails に 15 年間取り組んでいます。