メモ_Python-FlaskとMysqlで構築したWebアプリでデータ参照をpandasに変えて高速化

データを表示するFlaskアプリを作っていたところ、数万件程度のレコードであれば一瞬で終わるので問題ないのですが、レコード数が数百万件程度になってくると、Webページの最初の表示が4秒ぐらいかかるようになります。

数百万件レコードに対するsqlクエリー自体が3~4秒くらいになっているので当然で、2回目以降のアクセスはFlask内でのキャッシュで一瞬で表示されるのでそれでも良いのですが、なんとか簡易的に初回表示から速くできないものかと思って、試しにMysqlではなくPandasからデータをとるようにしたら、初回アクセスでも一瞬で表示されるようになりました(まあ、Pandasはインメモリなので自明かもしれませんが)

そのかわり、
・Flaskアプリの起動自体が重くなる(mysqlからpandasにロードする時間がかかる)
・メモリを大量に消費する(mysqlのtableが600MB程度だけど、pandasに展開すると、それだけで3GBくらい消費)
などのデメリットもあるのと、このスキームで安定稼働するかは不明なので、開発中アプリをプレゼン用に一時的に高速化したい場合などに限定した方法かな。

 

コード例:

以下は普通にmysqlに接続してFlaskアプリで表示する場合


from flask import Flask, render_template, request, redirect
from flask_caching import Cache
from threading import Lock
from configparser import ConfigParser

import mysql.connector
import json


app = Flask(__name__)

# コンフィグファイルの読み込み
config = ConfigParser()
config.read('config.ini')

# キャッシュの設定
cache = Cache(app, config={
    'CACHE_TYPE': 'simple',
    'CACHE_DEFAULT_TIMEOUT': 3600  # 1時間のキャッシュ有効期間を設定
})

# スレッドセーフなMySQL接続を確保するためのロックを作成
lock = Lock()


def get_db_connection():
    return mysql.connector.connect(
~中略~
    )


@cache.memoize()
def get_record_from_db(current_number):
    conn = get_db_connection()
    cursor = conn.cursor()
~中略~

    return record

if __name__ == '__main__':
    app.run()
    

これをpandasに接続してFlaskアプリで表示する場合


from flask import Flask, render_template, request, redirect
from flask_caching import Cache
from threading import Lock
from configparser import ConfigParser
import pandas as pd
import mysql.connector

app = Flask(__name__)

# コンフィグファイルの読み込み
config = ConfigParser()
config.read('config.ini')

# キャッシュの設定
cache = Cache(app, config={
    'CACHE_TYPE': 'simple',
    'CACHE_DEFAULT_TIMEOUT': 3600  # 1時間のキャッシュ有効期間を設定
})

# スレッドセーフなpandasデータフレームを保持するためのロックを作成
lock = Lock()

def load_data_from_mysql():
    # MySQLへの接続情報を取得
    db_config = {
~中略~
    }

    # MySQLからデータを取得してpandasデータフレームに変換
    conn = mysql.connector.connect(**db_config)
    query = ~中略~
    data = pd.read_sql(query, conn)

    return data

data = load_data_from_mysql()

@cache.memoize()
def get_record_from_data(current_number):
~中略~
    return record

@app.route('/')
def index():
~中略~

if __name__ == '__main__':
    app.run()