使用 Postgresql 和 Flask-SQLAlchemy 提高 COUNT(*) WHERE 的数据库性能

Zac*_*ary 1 postgresql sqlalchemy heroku flask

我有一个运行 SQLAlchemy 和 PostgresQL 的 Flask 应用程序来处理大量数据。我们在前端显示的一件事是一个仪表板,其中包含给定组织的几个汇总统计信息。最近这个端点运行得非常缓慢,所以我一直在尝试优化它并提高性能。

我首先对 BaseQuery 进行子类化,并在.count()不使用子查询的情况下实现内置的 SQLAlchemy 的精简版本。

优化查询

from sqlalchemy import func
from sqlalchemy.orm import lazyload
from flask_sqlalchemy import BaseQuery

class OptimisedQuery(BaseQuery):
    def optimised_count(query):
        count_query = query.options(lazyload('*')).statement.with_only_columns([func.count()]).order_by(None)
        return query.session.execute(count_query).scalar()
Run Code Online (Sandbox Code Playgroud)

api/dashboard.py

@dashboards.route("/api/dashboard/stats", methods=["GET"])
    @authentication_required
    def stats(current_user):
        org = current_user.organization

        total_subscribers = Subscriber.query.filter_by(
            unsubscribed=False, organization=org
        ).optimised_count()

        total_conversations = SubscriberConversationState.query.filter_by(
            organization=org
        ).optimised_count()

        total_messages = Message.query.filter_by(
            organization=org
        ).optimised_count()

        total_unsubscribers = Subscriber.query.filter_by(
            unsubscribed=True, organization=org
        ).optimised_count()

        return jsonify(
            dict(
                total_subscribers=total_subscribers,
                total_conversations=total_conversations,
                total_messages=total_messages,
                total_unsubscribers=total_unsubscribers,
            )
        )
Run Code Online (Sandbox Code Playgroud)

这绝对是朝着正确方向迈出的一步,显着降低了端点延迟。话虽如此,加载仍然需要 9-15 秒,所以我深入到 New Relic 并看到其中一个查询(在消息表上计数)仍然表现得很糟糕。下面的截图实际上是我能找到的最好的大约 1.5 秒,但有时需要 6 秒。

新遗迹踪迹

这是不是奇怪,因为消息表是最大的一群(4090065行)。然而,查看查询,它似乎尽可能精简,即使我要放弃 SQLAlchemy 并只编写纯 SQL。更奇怪的是,在将 prod db 克隆到我的本地机器并使用 分析相同查询后pgbench,查询运行速度快如闪电,平均延迟为 86.9 毫秒。

工作台

问题

  • 最终的简化查询,SELECT count(*) AS count_1 FROM message WHERE %(param_1)s = messages.organization_id是查询可以得到的最精简的吗?
  • 我发现了大量关于COUNT性能和提高性能的策略的文章,但没有关于COUNT WHERE查询的文章。在这种情况下,我可以在数据库构建中做些什么来加快速度?(索引等)
  • 是什么导致了pgbench86.9 毫秒的延迟与超过 6 秒的生产运行时间之间的差异?作为参考,该应用程序托管在 Heroku 上,并利用了 3 个 Standard-2X Web dynos 和一个 Postgres Standard-0 附加组件。

Lau*_*lbe 5

pgbench 运行不断organization_id重复要求相同的内容,因此数据可能已被缓存。此外,可能会有更多消息的组织。所以我对运行时不同并不感到惊讶。

由于您已经阅读了的性能count(*),因此我不会为您提供详细信息。我看到两个选项:

  1. 使用您定期刷新的物化视图:

    CREATE MATERIALIZED VIEW message_count AS
    SELECT organization_id, count(*) AS c
    FROM messages
    GROUP BY organization_id;
    
    CREATE UNIQUE INDEX ON message_count (organization_id);
    
    Run Code Online (Sandbox Code Playgroud)

    然后你会得到稍微陈旧的数据,但你会很快。

  2. 如果您需要精确计数,请制作message_count一个常规表并添加触发器以在修改时messages更新相应的message_countmessages

    这将减慢 上的数据修改速度messages,但如果您确实需要频繁且快速地进行计数,那么这可能是值得的。