You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI流式返回Aurora百万级查询结果过慢的优化咨询

问题描述

我们在Fargate集群中部署了一个FastAPI服务(配置16vCPU、32GB内存),该服务从拥有数百万条记录的Aurora单表查询数据。核心接口代码如下:

@trade_router.get('/trades')
def read_item(symbol: str, db: Session = Depends(get_db)):

    with db as session:
        trades = session.execute(text("SELECT * FROM trades WHERE symbol = :symbol"), {"symbol": symbol.upper()})

    def iter_trades():
        for trade in trades:
            # send as csv
            yield f"{trade.trade_date}, {trade.symbol}, {trade.price}, {trade.quantity}\n"

    return StreamingResponse(iter_trades(), media_type="application/json")

当前遇到的性能问题:

  • 数据库直接执行相同查询仅需约7秒
  • 通过接口返回700k条记录时,全部数据返回耗时长达2分钟
  • 客户端(C# .Net桌面应用、浏览器、Postman、curl)调用性能一致,均需约2分钟

我们有以下疑问:

  1. Python和FastAPI是否为这类场景的合适解决方案?
  2. 如何优化接口的返回速度?
  3. 是否需要通过物化视图、按symbol分区等方式让数据库完成更多处理?
  4. 参考建议提到“不应从FastAPI或任何Web服务器发起700k行的数据库请求”,该如何调整架构?
优化方案

1. 修复响应媒体类型错误

代码中实际返回CSV格式数据,但设置的media_type="application/json"会导致客户端/中间件做无效的格式解析,增加额外开销。应改为:

return StreamingResponse(iter_trades(), media_type="text/csv")

2. 优化数据库结果读取方式

当前代码中,session.execute默认会把结果集全部加载到内存(客户端游标),700k条数据会占用大量内存并增加IO时间。建议使用服务器端游标(Server-Side Cursor),让数据库分批返回数据:

with db as session:
    # 针对SQLAlchemy,设置stream_results=True启用服务器端游标
    trades = session.execute(
        text("SELECT * FROM trades WHERE symbol = :symbol"),
        {"symbol": symbol.upper()},
        stream_results=True
    )

如果使用PostgreSQL兼容的Aurora,还可以配合fetchmany(size=1000)批量读取,减少迭代时的数据库交互次数。

3. 减少Python字符串拼接开销

700k次循环中的字符串格式化(f-string)累计开销极大,建议使用标准库csv模块批量生成CSV内容,提升效率:

import csv
from io import StringIO

@trade_router.get('/trades')
def read_item(symbol: str, db: Session = Depends(get_db)):
    def iter_trades():
        output = StringIO()
        writer = csv.writer(output)
        # 写入表头
        writer.writerow(["trade_date", "symbol", "price", "quantity"])
        with db as session:
            trades = session.execute(
                text("SELECT trade_date, symbol, price, quantity FROM trades WHERE symbol = :symbol"),
                {"symbol": symbol.upper()},
                stream_results=True
            )
            for trade in trades:
                writer.writerow(trade)
                # 输出当前缓冲区内容并清空
                output.seek(0)
                yield output.read()
                output.seek(0)
                output.truncate()

    return StreamingResponse(iter_trades(), media_type="text/csv")

或者直接按批次拼接字符串,减少IO操作次数。

4. 数据库层面优化

  • 添加索引:确保symbol字段存在索引,进一步缩短数据库查询时间(即使当前查询7秒,索引能让过滤更高效):
    CREATE INDEX idx_trades_symbol ON trades(symbol);
    
  • 按symbol分区:如果查询几乎都是按symbol过滤,分区能让数据库仅扫描目标分区,大幅减少磁盘IO:
    -- 示例:按symbol列表分区(根据实际symbol类型选择分区方式)
    ALTER TABLE trades PARTITION BY LIST (symbol);
    
  • 物化视图:如果数据更新频率低,可预先生成每个symbol的物化视图,查询直接读取预计算结果:
    CREATE MATERIALIZED VIEW mv_trades_symbol_xxx AS
    SELECT trade_date, symbol, price, quantity FROM trades WHERE symbol = 'XXX';
    -- 定期刷新视图
    REFRESH MATERIALIZED VIEW mv_trades_symbol_xxx;
    

5. 调整架构:避免Web服务传输大结果集

参考建议提到的“不应从Web服务器发起700k行请求”,可调整架构:

  • 直接从数据库导出到对象存储:让Aurora把查询结果导出到S3,然后接口返回S3的临时下载链接,客户端直接从S3下载(速度远快于Web服务转发)。
  • 分页查询:如果业务允许,让客户端分批次请求数据(比如每次10k条),避免单次传输大量数据。

关于Python和FastAPI的适用性

Python和FastAPI完全适用于这类场景,当前的性能瓶颈并非框架本身,而是代码实现细节(媒体类型错误、客户端游标、字符串拼接开销)和数据传输方式导致的。通过上述优化,性能可大幅接近数据库直接查询的水平。


内容的提问来源于stack exchange,提问作者rmcsharry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:21:01