[英]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 大得離譜,會導致一些非常惱火的用戶。
gzip
和JSON
響應的聰明才智是不夠的,你需要做更多的工作。
幸運的是,有很多分頁問題和答案,而 Flasks 模塊化的處理方式意味着有人可能編寫了一個適用於您的問題的模塊。 我將從使用 ORM 重新實現該方法開始。 聽說sqlalchemy相當不錯。
TL;博士; 嘗試重組您的 JSON 有效負載(即更改架構)
我看到您正在您的一個 API 中構建 JSON 響應。 目前,您的 JSON 有效負載如下所示:
[
{
"col0": "val00",
"col1": "val01"
},
{
"col0": "val10",
"col1": "val11"
}
...
]
我建議您以 JSON 中的每個(第一級)鍵代表整個列的方式對其進行重組。 因此,對於上述情況,它將變為:
{
"col0": ["val00", "val10", "val20", ...],
"col1": ["val01", "val11", "val21", ...]
}
這是我執行的一些離線測試的結果。
實驗變量:
#!/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
在這種情況下,它幾乎減少了一半。
我建議您執行以下操作:
對於無法使用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中進行了描述。 對於每條記錄:
(請注意,所謂的 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.