簡體   English   中英

如何加快從 Python-Flask 應用程序返回 20MB Json 文件的速度?

[英]How to speed up returning a 20MB Json file from a Python-Flask application?

我正在嘗試調用 API 進而觸發我們的 sqlserver 數據庫中的存儲過程。 我就是這樣編碼的。

class Api_Name(Resource):

    def __init__(self):
        pass

    @classmethod
    def get(self):
        try:
            engine = database_engine
            connection = engine.connect()
            sql = "DECLARE @return_value int EXEC @return_value = [dbname].[dbo].[proc_name])
            return call_proc(sql, apiname, starttime, connection)
        except Exception as e:
            return {'message': 'Proc execution failed with error => {error}'.format(error=e)}, 400
        pass

call_proc是我從數據庫返回 JSON 的方法。

def call_proc(sql: str, connection):
    try:
        json_data = []
        rv = connection.execute(sql)
        for result in rv:
            json_data.append(dict(zip(result.keys(), result)))
        return Response(json.dumps(json_data), status=200)
    except Exception as e:
        return {'message': '{error}'.format(error=e)}, 400
    finally:
        connection.close()

output 的問題在於返回 JSON 的方式及其大小。 起初 API 需要 1 分 30 秒:當返回語句是這樣的:

case1: return Response(json.dumps(json_data), status=200, mimetype='application/json')

上網查了一下,發現上面的說法是想美化JSON。 所以我從響應中刪除了mimetype並將其設置為

case2: return Response(json.dumps(json_data), status=200)

API 運行 30 秒,盡管 JSON output 沒有正確對齊,但它仍然是 Z0ECD18ZA48。 我看到從 API 返回的 JSON 的 output 大小接近 20MB。 我在 postman 響應中觀察到這一點:

Status: 200 OK    Time: 29s    Size: 19MB

Json output的區別:

情況1:

[   {
        "col1":"val1",
        "col2":"val2"
    },
    {
        "col1":"val1",
        "col2":"val2"
    }
]

案例2:

[{"col1":"val1","col2":"val2"},{"col1":"val1","col2":"val2"}]

output與上述兩種情況的區別會不同嗎? 如果是這樣,我該如何解決這個問題? 如果沒有區別,有什么辦法可以進一步加快速度並進一步減少運行時間,比如壓縮我要返回的 JSON?

您可以使用gzip壓縮來使純文本的重量從兆字節到千字節。 甚至為此使用燒瓶壓縮庫。
另外我建議使用ujson來使dump()調用更快。

import gzip

from flask import make_response
import ujson as json


@app.route('/data.json')
def compress():
    compression_level = 5  # of 9 max
    data = [
        {"col1": "val1", "col2": "val2"},
        {"col1": "val1", "col2": "val2"}
    ]
    content = gzip.compress(json.dumps(data).encode('utf8'), compression_level)
    response = make_response(content)
    response.headers['Content-length'] = len(content)
    response.headers['Content-Encoding'] = 'gzip'
    return response

文檔:

首先,配置文件:如果 90% 的時間用於通過網絡傳輸,則優化處理速度不如優化傳輸速度有用(例如,按照wowkin 推薦的方式壓縮響應(盡管 web 服務器可能配置為這會自動,如果您使用的是一個)

假設構建 JSON 很慢,如果您控制數據庫代碼,您可以使用它的JSON 功能來序列化數據,並避免在 ZA7F5F35426B9274173Z 層執行此操作。 例如,

SELECT col1, col2
FROM tbl
WHERE col3 > 42
FOR JSON AUTO

會給你

[
    {
        "col1": "foo",
        "col2": 1
    },
    {
        "col1": "bar",
        "col2": 2
    },
    ...
]

也可以創建嵌套結構,如文檔中所述。

如果請求者只需要數據,請使用燒瓶的send_file功能將其作為下載返回,並避免構建 HTML 響應的成本:

from io import BytesIO
from flask import send_file

def call_proc(sql: str, connection):
    try:
        rv = connection.execute(sql)
        json_data = rv.fetchone()[0]
        # BytesIO expects encoded data; if you can get the server to encode 
        # the data instead it may be faster.
        encoded_json = json_data.encode('utf-8')
        buf = BytesIO(encoded_json)
        return send_file(buf, mimetype='application/json', as_attachment=True, conditional=True) 
    except Exception as e:
        return {'message': '{error}'.format(error=e)}, 400
    finally:
        connection.close()

您需要在 API 上實現分頁。 19MB 大得離譜,會導致一些非常惱火的用戶。

gzipJSON響應的聰明才智是不夠的,你需要做更多的工作。

幸運的是,有很多分頁問題和答案,而 Flasks 模塊化的處理方式意味着有人可能編寫了一個適用於您的問題的模塊。 我將從使用 ORM 重新實現該方法開始。 聽說sqlalchemy相當不錯。

要回答您的問題:

1 - JSON 在語義上是相同的。 您可以使用http://www.jsondiff.com來比較兩個 JSON。

2 - 我建議您制作數據塊並通過網絡發送。

這可能會有所幫助: https://masnun.com/2016/09/18/python-using-the-requests-module-to-download-large-files-efficiently.html

TL;博士; 嘗試重組您的 JSON 有效負載(即更改架構)

我看到您正在您的一個 API 中構建 JSON 響應。 目前,您的 JSON 有效負載如下所示:

[
  {
    "col0": "val00",
    "col1": "val01"
  },
  {
    "col0": "val10",
    "col1": "val11"
  }
  ...
]

我建議您以 JSON 中的每個(第一級)鍵代表整個列的方式對其進行重組。 因此,對於上述情況,它將變為:

{
  "col0": ["val00", "val10", "val20", ...],
  "col1": ["val01", "val11", "val21", ...]
}

這是我執行的一些離線測試的結果。

實驗變量:

  • NUMBER_OF_COLUMNS = 10
  • NUMBER_OF_ROWS = 100000
  • LENGTH_OF_STR_DATA = 5
#!/usr/bin/env python3

import json

NUMBER_OF_COLUMNS = 10
NUMBER_OF_ROWS = 100000
LENGTH_OF_STR_DATA = 5

def get_column_name(id_): 
    return 'col%d' % id_ 

def random_data(): 
    import string 
    import random 
    return ''.join(random.choices(string.ascii_letters, k=LENGTH_OF_STR_DATA))

def get_row(): 
    return { 
        get_column_name(i): random_data() 
        for i in range(NUMBER_OF_COLUMNS) 
    }

# data1 has same schema as your JSON
data1 = [ 
    get_row() for _ in range(NUMBER_OF_ROWS) 
]

with open("/var/tmp/1.json", "w") as f: 
    json.dump(data1, f) 

def get_column(): 
    return [random_data() for _ in range(NUMBER_OF_ROWS)] 

# data2 has the new proposed schema, to help you reduce the size
data2 = { 
    get_column_name(i): get_column() 
    for i in range(NUMBER_OF_COLUMNS) 
}

with open("/var/tmp/2.json", "w") as f: 
    json.dump(data2, f) 

比較兩個 JSON 的大小:

$ du -h /var/tmp/1.json
17M

$ du -h /var/tmp/2.json
8.6M

在這種情況下,它幾乎減少了一半。

我建議您執行以下操作:

  • 首先,分析您的代碼以查看真正的罪魁禍首。 如果它確實是有效負載大小,請繼續。
  • 嘗試更改 JSON 的架構(如上所述)
  • 在發送之前壓縮您的有效負載(從您的 Flask WSGI 應用程序層或您的網絡服務器級別 - 如果您在一些生產級網絡服務器后面運行 Flask 應用程序,例如 ZE9713AE04A02A810D6F33DD956F4279 或 4Nginx)

對於無法使用ndjson (或任何類型的分隔記錄格式)之類的內容進行分頁的大數據,可以真正減少所需的服務器資源,因為您會阻止在 ZCD69B4957F06CD818DZBF3E26 中保存 JSON object 您需要訪問響應 stream 才能將每個對象/行寫入響應。

響應

[   {
        "col1":"val1",
        "col2":"val2"
    },
    {
        "col1":"val1",
        "col2":"val2"
    }
]

最終看起來像

{"col1":"val1","col2":"val2"}
{"col1":"val1","col2":"val2"}

這對客戶端也有好處,因為您也可以自己解析和處理每一行。

如果您不處理響應 CSV 的嵌套數據結構,那么它會更小。

我想指出,有一種標准方法可以在 JSON 中編寫一系列單獨的記錄,並在RFC 7464中進行了描述。 對於每條記錄:

  1. 寫入記錄分隔符字節 (0x1E)。
  2. 在 UTF-8 中寫入 JSON 記錄,這是一個常規的 JSON 文檔,也可以包含內部換行符。
  3. 寫入換行字節 (0x0A)。

(請注意,所謂的 JSON 文本序列格式使用更自由的語法來解析此類文本序列;有關詳細信息,請參閱 RFC。)

在您的示例中,JSON 文本序列如下所示,其中\x1E\x0A分別是記錄分隔符和換行字節:

 \x1E{"col1":"val1","col2":"val2"}\x0A\x1E{"col1":"val1","col2":"val2"}\x0A

由於 JSON 文本序列格式允許內部換行符,因此您可以按自然方式編寫每個 JSON 記錄,如下例所示:

 \x1E{
    "col1":"val1",
    "col2":"val2"}
 \x0A\x1E{
    "col1":"val1",
    "col2":"val2"
 }\x0A

請注意,JSON 文本序列的媒體類型不是application/json ,而是application/json-seq 請參閱 RFC。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM