提高 Rails 控制器方法的性能,returns 序列化哈希

Improving performance of a Rails controller method that returns a serialized hash

是否有人愿意就如何提高以下控制器方法的性能给我建议?

def index
    @contacts = Hash[current_user.company.contacts.map {|contact| [contact.id, ContactSerializer.new(contact).as_json[:contact]] }]

    respond_to do |format|
        format.json { render json: { contacts: @contacts } }
    end
end

这 return 具有以下数据结构:

{
    contacts: {
        79: {
            id: 79,
            first_name: "Foo",
            last_name: "Bar",
            email: "t@t.co",
            engagement: "0%",
            company_id: 94,
            created_at: " 9:41AM Jan 30, 2016",
            updated_at: "10:57AM Feb 23, 2016",
            published_response_count: 0,
            groups: {
                test: true,
                test23: false,
                Test222: false,
                Last: false
            },
            invites: [
                {
                    id: 112,
                    email: "t@t.co",
                    status: "Requested",
                    created_at: "Jan 30, 2016, 8:48 PM",
                    date_submitted: null,
                    response: null
                }
            ],
            responses: [ ],
            promotions: [
                {
                    id: 26,
                    company_id: 94,
                    key: "e5cb3bc80b58c29df8a61231d0",
                    updated_at: "Feb 11, 2016, 2:45 PM",
                    read: null,
                    social_media_posts: [ ]
                }
            ]
        },
        81: {
            id: 81,
            first_name: "Foo2",
            last_name: "Bar2",
            email: "foobar2@foobar.com",
            engagement: "0%",
            company_id: 94,
            created_at: "12:55PM Feb 04, 2016",
            updated_at: " 4:25PM Feb 19, 2016",
            published_response_count: 0,
            groups: {
                test: true,
                test23: true,
                Test222: false,
                Last: false
            },
            invites: [
                {
                    id: 116,
                    email: "foobar2@foobar.com",
                    status: "Requested",
                    created_at: "Feb 22, 2016, 9:10 PM",
                    date_submitted: null,
                    response: null
                }
            ],
            responses: [ ],
            promotions: [
                {
                    id: 26,
                    company_id: 94,
                    key: "e5cb3bc80b58c29df8a61231d0",
                    updated_at: "Feb 11, 2016, 2:45 PM",
                    read: null,
                    social_media_posts: [ ]
                }
            ]
        }
    }
}

我需要 index 方法来 return 散列,其中键是联系人 ID,而不是通常 returned 的数组。此外,我通过序列化程序传递每个联系人,以便我获得客户需要的所有关联数据。

只有几个联系人时,此方法工作正常,但是当我有 100 或 1000 个联系人时,它确实变慢了。我用 100 个联系人对它进行了基准测试,花了 4 秒才完成,这太糟糕了。我想知道如何改进我的代码,以更高效的方式获得 完全相同的 输出。这里的关键是输出需要保持不变。我对修改客户端代码没有兴趣(许多应用程序都依赖于此数据结构),因此所有更改都需要在服务器端进行。

这是我的ContactSerializer供参考:

class ContactSerializer < ActiveModel::Serializer
    attributes :id, :first_name, :last_name, :email, :engagement, :company_id, :created_at, :updated_at, :published_response_count, :groups
    has_many :invites
    has_many :responses
    has_many :promotions

    def groups
        Hash[object.company.groups.map {|group| [group.name, object.groups.include?(group)] }]
    end

    def published_response_count
        object.responses.where(published: true).count
    end

    def created_at
        object.created_at.in_time_zone("Eastern Time (US & Canada)").strftime("%l:%M%p %b %d, %Y")
    end

    def updated_at
        object.updated_at.in_time_zone("Eastern Time (US & Canada)").strftime("%l:%M%p %b %d, %Y")
    end

    def engagement
        object.engagement
    end
end

对于它的价值,我完全意识到 returning JSON 来自 Rails 的数据并不是一个很好的做法,并且已经完全放弃了它。不幸的是,这段代码是很久以前写的,我没有时间完全重写客户端以使用标准输出,例如联系人数组。

我开始研究 ActiveRecord 生成的查询。

一段时间后我意识到查询只占总处理时间的几毫秒。我开始从基本查询 company.contacts 开始对代码进行基准测试,然后逐渐添加我拥有的方法,例如 map 然后将每个联系人传递到序列化程序中,最后调用 as_json[:contact] ContactSerializer.new(contact) 返回的对象。

我发现在序列化对象上调用 as_json[:contact] 每次联系平均消耗大约 30 毫秒(平均超过 100 次运行)。我这样做的原因是从返回的 JSON 中删除根 contact 节点。

当我用 198 个联系人对我的原始代码进行基准测试时,10 次运行平均花费 10400 毫秒。当我删除 as_json[:contact] 并在 ContactSerializer 上设置 root false 时,如“Abusing ActiveModel::Serializers for HAL”所述,我能够将时间从 10400 毫秒减少到 87 毫秒,同时返回确切的与客户端相同的结构,这令人震惊。

通过一些查询优化可能会缩短几毫秒,但此时其他任何事情都只是锦上添花。